@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.
- package/Package.swift +77 -0
- package/README.md +22 -14
- package/package.json +11 -2
- package/plugin.xml +4 -4
- package/src/android/PurchaselyPlugin.kt +247 -67
- package/src/ios/CDVPurchasely+Events.m +7 -1
- package/src/ios/CDVPurchasely.h +11 -0
- package/src/ios/CDVPurchasely.m +180 -10
- package/src/ios/Hybrid/PLYPlan+Hybrid.m +19 -0
- package/src/ios/Hybrid/PLYSubscription+Hybrid.m +13 -0
- package/www/Purchasely.js +390 -46
- package/__tests__/Purchasely.test.js +0 -1161
- package/example/android.sh +0 -10
- package/example/config.xml +0 -27
- package/example/e2e/README.md +0 -44
- package/example/e2e/helpers/driver.js +0 -70
- package/example/e2e/package.json +0 -19
- package/example/e2e/specs/bridge.e2e.js +0 -51
- package/example/e2e/specs/dismiss.e2e.js +0 -37
- package/example/e2e/tools/ci_run_e2e.sh +0 -49
- package/example/e2e/tools/ci_run_e2e_ios.sh +0 -46
- package/example/e2e/wdio.android.conf.js +0 -21
- package/example/e2e/wdio.ios.conf.js +0 -19
- package/example/e2e/wdio.shared.conf.js +0 -28
- package/example/ios.sh +0 -16
- package/example/package-lock.json +0 -859
- package/example/package.json +0 -37
- package/example/www/css/index.css +0 -110
- package/example/www/img/logo.png +0 -0
- package/example/www/index.html +0 -78
- package/example/www/js/index.js +0 -449
- package/purchasely.iml +0 -11
package/src/ios/CDVPurchasely.m
CHANGED
|
@@ -13,14 +13,20 @@
|
|
|
13
13
|
|
|
14
14
|
@implementation CDVPurchasely
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|