@purchasely/cordova-plugin-purchasely 6.0.0-rc.1 → 6.0.0

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.
@@ -110,13 +110,22 @@
110
110
 
111
111
  - (void)userLogin:(CDVInvokedUrlCommand*)command {
112
112
  NSString *userId = [command argumentAtIndex:0];
113
+ // CDV-W-08: userLoginWith:appUserId: is _Nonnull; align with Android's guard instead of
114
+ // forwarding nil into the native call.
115
+ if (![userId isKindOfClass:[NSString class]]) {
116
+ [self successFor:command resultBool:NO];
117
+ return;
118
+ }
113
119
  [Purchasely userLoginWith:userId shouldRefresh:^(BOOL refresh) {
114
120
  [self successFor:command resultBool:refresh];
115
121
  }];
116
122
  }
117
123
 
118
124
  - (void)userLogout:(CDVInvokedUrlCommand*)command {
119
- [Purchasely userLogout:YES];
125
+ // PAR-30: clearUserAttributes defaults to true (matches the JS-side default).
126
+ NSNumber *clearUserAttributes = [command argumentAtIndex:0];
127
+ BOOL clear = [clearUserAttributes isKindOfClass:[NSNumber class]] ? clearUserAttributes.boolValue : YES;
128
+ [Purchasely userLogout:clear];
120
129
  }
121
130
 
122
131
  - (void)setThemeMode:(CDVInvokedUrlCommand *)command {
@@ -201,6 +210,9 @@
201
210
  case CordovaPLYAttributeBatchCustomUserId:
202
211
  attribute = PLYAttributeBatchCustomUserId;
203
212
  break;
213
+ case CordovaPLYAttributeOneSignalUserId:
214
+ attribute = PLYAttributeOneSignalUserId;
215
+ break;
204
216
  default:
205
217
  attributeFound = NO;
206
218
  break;
@@ -219,6 +231,11 @@
219
231
  [self successFor:command resultString:anonymousId];
220
232
  }
221
233
 
234
+ // REC-12 / PAR-04
235
+ - (void)isAnonymous:(CDVInvokedUrlCommand*)command {
236
+ [self successFor:command resultBool:[Purchasely isAnonymous]];
237
+ }
238
+
222
239
  - (void)allowDeeplink:(CDVInvokedUrlCommand*)command {
223
240
  BOOL allow = [[command argumentAtIndex:0] boolValue];
224
241
  [Purchasely allowDeeplink: allow];
@@ -276,7 +293,7 @@
276
293
  [r setKeepCallbackAsBool:YES];
277
294
  [strongSelf.commandDelegate sendPluginResult:r callbackId:callbackId];
278
295
  }];
279
- builder = [builder onClose:^{
296
+ builder = [builder onCloseRequested:^{
280
297
  CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
281
298
  CDVPluginResult *r = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"event": @"closeRequested"}];
282
299
  [r setKeepCallbackAsBool:YES];
@@ -408,6 +425,11 @@
408
425
 
409
426
  - (void)purchasedSubscription:(CDVInvokedUrlCommand*)command {
410
427
  self.purchasedCommand = command;
428
+ // CDV-W-14: a repeat JS call (re-subscribe/hot reload) must not stack observers, or a
429
+ // single purchase/restoration fires reloadContent: once per accumulated registration.
430
+ [[NSNotificationCenter defaultCenter] removeObserver:self
431
+ name: @"ply_purchasedSubscription"
432
+ object:nil];
411
433
  [[NSNotificationCenter defaultCenter] addObserver:self
412
434
  selector:@selector(reloadContent:)
413
435
  name: @"ply_purchasedSubscription"
@@ -455,7 +477,10 @@
455
477
  }
456
478
 
457
479
  - (void)userSubscriptions:(CDVInvokedUrlCommand*)command {
458
- [Purchasely userSubscriptions:false
480
+ // PAR-29: invalidateCache defaults to false.
481
+ NSNumber *invalidateCache = [command argumentAtIndex:0];
482
+ BOOL invalidate = [invalidateCache isKindOfClass:[NSNumber class]] ? invalidateCache.boolValue : NO;
483
+ [Purchasely userSubscriptions:invalidate
459
484
  success:^(NSArray<PLYSubscription *> * _Nullable subscriptions) {
460
485
  NSMutableArray *result = [NSMutableArray new];
461
486
  for (PLYSubscription *subscription in subscriptions) {
@@ -469,7 +494,10 @@
469
494
  }
470
495
 
471
496
  - (void)userSubscriptionsHistory:(CDVInvokedUrlCommand*)command {
472
- [Purchasely userSubscriptionsHistory:false
497
+ // PAR-29: invalidateCache defaults to false.
498
+ NSNumber *invalidateCache = [command argumentAtIndex:0];
499
+ BOOL invalidate = [invalidateCache isKindOfClass:[NSNumber class]] ? invalidateCache.boolValue : NO;
500
+ [Purchasely userSubscriptionsHistory:invalidate
473
501
  success:^(NSArray<PLYSubscription *> * _Nullable subscriptions) {
474
502
  NSMutableArray *result = [NSMutableArray new];
475
503
  for (PLYSubscription *subscription in subscriptions) {
@@ -530,7 +558,10 @@
530
558
  if (@available(iOS 12.2, *)) {
531
559
  [Purchasely signPromotionalOfferWithStoreProductId:storeProductId storeOfferId:storeOfferId success:^(PLYOfferSignature * _Nonnull signature) {
532
560
  NSDictionary* result = [self resultSignatureForSignPromoOffer:signature];
533
- [self successFor:command resultBool:result];
561
+ // CDV-W-02: was resultBool: (an NSDictionary implicitly truncated to a bare
562
+ // BOOL), discarding the whole signature payload. resultDict: is the matching
563
+ // overload (declared below) for this NSDictionary result.
564
+ [self successFor:command resultDict:result];
534
565
  } failure:^(NSError * _Nullable error) {
535
566
  [self failureFor:command resultString:error.localizedDescription];
536
567
  }];
@@ -553,15 +584,62 @@
553
584
  }];
554
585
  }
555
586
 
587
+ // PAR-05: Dynamic Offerings. Payload keys (reference/planVendorId/offerVendorId) match the
588
+ // Cordova JS↔native contract (not the native SDK's own planId/offerId property names).
589
+ - (void)setDynamicOffering:(CDVInvokedUrlCommand*)command {
590
+ NSString *reference = [command argumentAtIndex:0];
591
+ NSString *planVendorId = [command argumentAtIndex:1];
592
+ NSString *offerVendorId = [command argumentAtIndex:2];
593
+ if (![offerVendorId isKindOfClass:[NSString class]]) {
594
+ offerVendorId = nil;
595
+ }
596
+ // iOS 26.4+ Apple commitment billing plan type (0 unspecified / 1 upFront / 2 monthly);
597
+ // JS defaults it to unspecified when omitted (see Purchasely.BillingPlanType).
598
+ PLYBillingPlanType billingPlanType = [[command argumentAtIndex:3 withDefault:@(PLYBillingPlanTypeUnspecified)] integerValue];
599
+
600
+ [Purchasely setDynamicOfferingWithReference:reference
601
+ planVendorId:planVendorId
602
+ offerVendorId:offerVendorId
603
+ billingPlanType:billingPlanType
604
+ completion:^(BOOL success) {
605
+ [self successFor:command resultBool:success];
606
+ }];
607
+ }
608
+
609
+ - (void)getDynamicOfferings:(CDVInvokedUrlCommand*)command {
610
+ [Purchasely getDynamicOfferingsWithCompletion:^(NSArray<PLYOffering *> * _Nonnull offerings) {
611
+ NSMutableArray *result = [NSMutableArray new];
612
+ for (PLYOffering *offering in offerings) {
613
+ NSMutableDictionary<NSString *, id> *dict = [NSMutableDictionary new];
614
+ dict[@"reference"] = offering.reference;
615
+ dict[@"planVendorId"] = offering.planId;
616
+ dict[@"offerVendorId"] = offering.offerId ?: [NSNull null];
617
+ [result addObject:dict];
618
+ }
619
+ [self successFor:command resultArray:result];
620
+ }];
621
+ }
622
+
623
+ - (void)removeDynamicOffering:(CDVInvokedUrlCommand*)command {
624
+ NSString *reference = [command argumentAtIndex:0];
625
+ if ([reference isKindOfClass:[NSString class]]) {
626
+ [Purchasely removeDynamicOfferingWithReference:reference];
627
+ }
628
+ }
629
+
630
+ - (void)clearDynamicOfferings:(CDVInvokedUrlCommand*)command {
631
+ [Purchasely clearDynamicOfferings];
632
+ }
633
+
556
634
  // Helpers
557
635
 
558
- // v6: builds a PLYDisplayMode from the JS transition object (see normalizeTransition):
636
+ // v6: builds a PLYTransition from the JS transition object (see normalizeTransition):
559
637
  // { type, dismissible?, height?: { type: 'pixel'|'percentage', value }, backgroundColor? }
560
638
  // (a bare mode string is also tolerated). Objective-C only exposes percentage height for
561
639
  // drawer/popin (pixel height and popin width are Swift-only in the SDK), so those honor a
562
640
  // percentage height + dismissible + background colors; other dimensions are ignored.
563
641
  // Returns nil for an unknown/absent value so the backend-defined default is honored.
564
- - (PLYDisplayMode * _Nullable) displayModeFromTransition:(id) transition {
642
+ - (PLYTransition * _Nullable) displayModeFromTransition:(id) transition {
565
643
  NSDictionary *map = [transition isKindOfClass:[NSDictionary class]] ? transition : nil;
566
644
  NSString *type = map != nil ? map[@"type"] : ([transition isKindOfClass:[NSString class]] ? transition : nil);
567
645
  if (![type isKindOfClass:[NSString class]]) {
@@ -593,17 +671,17 @@
593
671
  }
594
672
 
595
673
  if ([type isEqualToString:@"fullScreen"]) {
596
- return [PLYDisplayMode fullScreen];
674
+ return [PLYTransition fullScreen];
597
675
  } else if ([type isEqualToString:@"modal"]) {
598
- return [PLYDisplayMode modalWithDismissible:dismissible];
676
+ return [PLYTransition modalWithDismissible:dismissible];
599
677
  } else if ([type isEqualToString:@"push"]) {
600
- return [PLYDisplayMode push];
678
+ return [PLYTransition push];
601
679
  } else if ([type isEqualToString:@"inlinePaywall"]) {
602
- return [PLYDisplayMode inlinePaywall];
680
+ return [PLYTransition inlinePaywall];
603
681
  } else if ([type isEqualToString:@"drawer"]) {
604
- return [[PLYDisplayMode alloc] initWithType:PLYDisplayModeTypeDrawer heightPercentage:heightPercentage backgroundColors:backgroundColors dismissible:dismissible];
682
+ return [[PLYTransition alloc] initWithType:PLYTransitionTypeDrawer heightPercentage:heightPercentage backgroundColors:backgroundColors dismissible:dismissible];
605
683
  } else if ([type isEqualToString:@"popin"]) {
606
- return [[PLYDisplayMode alloc] initWithType:PLYDisplayModeTypePopin heightPercentage:heightPercentage backgroundColors:backgroundColors dismissible:dismissible];
684
+ return [[PLYTransition alloc] initWithType:PLYTransitionTypePopin heightPercentage:heightPercentage backgroundColors:backgroundColors dismissible:dismissible];
607
685
  }
608
686
  return nil;
609
687
  }
@@ -736,8 +814,8 @@
736
814
  }
737
815
  id<PLYPresentation> presentation = info.presentation;
738
816
  if (presentation != nil) {
739
- if (presentation.id != nil) {
740
- [infosResult setObject:presentation.id forKey:@"presentationId"];
817
+ if (presentation.screenId != nil) {
818
+ [infosResult setObject:presentation.screenId forKey:@"presentationId"];
741
819
  }
742
820
  if (presentation.placementId != nil) {
743
821
  [infosResult setObject:presentation.placementId forKey:@"placementId"];
@@ -890,6 +968,9 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
890
968
  completion(result);
891
969
  }
892
970
 
971
+ // PAR-19: closeAllScreens is the canonical native action; closePresentation is kept as a
972
+ // separate (deprecated, fire-and-forget) action since existing native `close` behavior is
973
+ // preserved as-is, while closeAllScreens additionally reports success/error to JS.
893
974
  - (void)closePresentation:(CDVInvokedUrlCommand*)command {
894
975
  dispatch_async(dispatch_get_main_queue(), ^{
895
976
  [Purchasely closeAllScreens];
@@ -897,6 +978,14 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
897
978
  });
898
979
  }
899
980
 
981
+ - (void)closeAllScreens:(CDVInvokedUrlCommand*)command {
982
+ dispatch_async(dispatch_get_main_queue(), ^{
983
+ [Purchasely closeAllScreens];
984
+ self.currentPresentation = nil;
985
+ [self successFor:command];
986
+ });
987
+ }
988
+
900
989
  - (void)backPresentation:(CDVInvokedUrlCommand*)command {
901
990
  dispatch_async(dispatch_get_main_queue(), ^{
902
991
  if (self.currentPresentation != nil) {
@@ -1070,6 +1159,34 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1070
1159
  }
1071
1160
  }
1072
1161
 
1162
+ // REC-12 / PAR-03: bulk read, same per-value conversion as the single-key read above.
1163
+ - (void)userAttributes:(CDVInvokedUrlCommand*)command {
1164
+ NSMutableDictionary<NSString *, id> *result = [NSMutableDictionary new];
1165
+ NSDictionary<NSString *, id> *attributes = [Purchasely userAttributes];
1166
+ for (NSString *key in attributes) {
1167
+ id value = [self getUserAttributeValueForCordova:attributes[key]];
1168
+ if (value != nil) {
1169
+ result[key] = value;
1170
+ }
1171
+ }
1172
+ [self successFor:command resultDict:result];
1173
+ }
1174
+
1175
+ // REC-12 / PAR-02
1176
+ - (void)incrementUserAttribute:(CDVInvokedUrlCommand*)command {
1177
+ NSString *key = [command argumentAtIndex:0];
1178
+ NSNumber *valueNumber = [command argumentAtIndex:1];
1179
+ NSInteger value = [valueNumber isKindOfClass:[NSNumber class]] ? valueNumber.integerValue : 1;
1180
+ [Purchasely incrementUserAttributeWithKey:key value:value];
1181
+ }
1182
+
1183
+ - (void)decrementUserAttribute:(CDVInvokedUrlCommand*)command {
1184
+ NSString *key = [command argumentAtIndex:0];
1185
+ NSNumber *valueNumber = [command argumentAtIndex:1];
1186
+ NSInteger value = [valueNumber isKindOfClass:[NSNumber class]] ? valueNumber.integerValue : 1;
1187
+ [Purchasely decrementUserAttributeWithKey:key value:value];
1188
+ }
1189
+
1073
1190
  - (id _Nullable) getUserAttributeValueForCordova:(id _Nullable) value {
1074
1191
  if ([value isKindOfClass:[NSDate class]]) {
1075
1192
  NSDateFormatter * dateFormatter = [NSDateFormatter new];
@@ -1095,6 +1212,31 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1095
1212
  [Purchasely clearBuiltInAttributes];
1096
1213
  }
1097
1214
 
1215
+ // PAR-07
1216
+ - (void)getBuiltInAttributes:(CDVInvokedUrlCommand*)command {
1217
+ NSMutableDictionary<NSString *, id> *result = [NSMutableDictionary new];
1218
+ NSDictionary<NSString *, id> *attributes = [Purchasely getBuiltInAttributes];
1219
+ for (NSString *key in attributes) {
1220
+ id value = [self getUserAttributeValueForCordova:attributes[key]];
1221
+ if (value != nil) {
1222
+ result[key] = value;
1223
+ }
1224
+ }
1225
+ [self successFor:command resultDict:result];
1226
+ }
1227
+
1228
+ - (void)getBuiltInAttribute:(CDVInvokedUrlCommand*)command {
1229
+ NSString *key = [command argumentAtIndex:0];
1230
+ id _Nullable result = [self getUserAttributeValueForCordova:[Purchasely getBuiltInAttributeWith:key]];
1231
+ if (result != nil) {
1232
+ [self successFor:command resultDict:result];
1233
+ } else {
1234
+ // No attribute for this key: resolve success with no value (undefined in JS),
1235
+ // matching Android's nullable Any? return.
1236
+ [self successFor:command];
1237
+ }
1238
+ }
1239
+
1098
1240
  - (void)fetchPresentation:(CDVInvokedUrlCommand*)command {
1099
1241
  NSString *placementId = [command argumentAtIndex:0];
1100
1242
  NSString *presentationId = [command argumentAtIndex:1];
@@ -1176,7 +1318,7 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1176
1318
  [r setKeepCallbackAsBool:YES];
1177
1319
  [strongSelf.commandDelegate sendPluginResult:r callbackId:callbackId];
1178
1320
  };
1179
- presentationLoaded.onClose = ^{
1321
+ presentationLoaded.onCloseRequested = ^{
1180
1322
  CDVPurchasely *strongSelf = weakSelf; if (strongSelf == nil) return;
1181
1323
  CDVPluginResult *r = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{@"event": @"closeRequested"}];
1182
1324
  [r setKeepCallbackAsBool:YES];
@@ -1224,7 +1366,7 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1224
1366
 
1225
1367
  - (id<PLYPresentation>) findPresentationLoadedFor:(NSString * _Nullable) presentationId {
1226
1368
  for (id<PLYPresentation> presentationLoaded in self.presentationsLoaded) {
1227
- if ([presentationLoaded.id isEqualToString: presentationId]) {
1369
+ if ([presentationLoaded.screenId isEqualToString: presentationId]) {
1228
1370
  return presentationLoaded;
1229
1371
  }
1230
1372
  }
@@ -1234,7 +1376,7 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1234
1376
  - (NSInteger) findIndexPresentationLoadedFor:(NSString * _Nullable) presentationId {
1235
1377
  NSInteger index = 0;
1236
1378
  for (id<PLYPresentation> presentationLoaded in self.presentationsLoaded) {
1237
- if ([presentationLoaded.id isEqualToString: presentationId]) {
1379
+ if ([presentationLoaded.screenId isEqualToString: presentationId]) {
1238
1380
  return index;
1239
1381
  }
1240
1382
  index++;
@@ -1269,8 +1411,16 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1269
1411
 
1270
1412
  if (presentation != nil) {
1271
1413
 
1272
- if (presentation.id != nil) {
1273
- [presentationResult setObject:presentation.id forKey:@"id"];
1414
+ if (presentation.screenId != nil) {
1415
+ // `screenId` is the sole authoritative, public presentation identifier (matches
1416
+ // Android's presentationToMap() key, so shared JS reads either platform the same
1417
+ // way). `id` is kept ONLY as a private/internal re-display lookup key --
1418
+ // findPresentationLoadedFor:/findIndexPresentationLoadedFor: key off it, and on
1419
+ // this platform it always equals screenId (iOS has no separate synthetic fetch
1420
+ // handle, unlike Android's `fetchId`) -- it is NOT a documented public field; the
1421
+ // JS bridge normalizes with `screenId ?? id` tolerance and never surfaces `id`.
1422
+ [presentationResult setObject:presentation.screenId forKey:@"id"];
1423
+ [presentationResult setObject:presentation.screenId forKey:@"screenId"];
1274
1424
  }
1275
1425
 
1276
1426
  if (presentation.placementId != nil) {
@@ -1411,7 +1561,8 @@ static BOOL PLYPresentationActionFromString(NSString *kind, PLYPresentationActio
1411
1561
  }
1412
1562
 
1413
1563
 
1414
- // WARNING: This enum must be strictly identical to the one in the JS side (Purchasely.js).
1564
+ // WARNING: This enum must be strictly identical (same declaration order) to the one in
1565
+ // the JS side (Purchasely.js) and Android's CordovaPLYAttribute enum class.
1415
1566
  typedef NS_ENUM(NSInteger, CordovaPLYAttribute) {
1416
1567
  CordovaPLYAttributeFirebaseAppInstanceId,
1417
1568
  CordovaPLYAttributeAirshipChannelId,
@@ -1433,7 +1584,8 @@ typedef NS_ENUM(NSInteger, CordovaPLYAttribute) {
1433
1584
  CordovaPLYAttributeAmplitudeDeviceId,
1434
1585
  CordovaPLYAttributeMoengageUniqueId,
1435
1586
  CordovaPLYAttributeOneSignalExternalId,
1436
- CordovaPLYAttributeBatchCustomUserId
1587
+ CordovaPLYAttributeBatchCustomUserId,
1588
+ CordovaPLYAttributeOneSignalUserId
1437
1589
  };
1438
1590
 
1439
1591
  @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