diff --git a/iphone/CoreApi/CoreApi.xcodeproj/project.pbxproj b/iphone/CoreApi/CoreApi.xcodeproj/project.pbxproj index 380b6d8e6a..fe6ab9d79c 100644 --- a/iphone/CoreApi/CoreApi.xcodeproj/project.pbxproj +++ b/iphone/CoreApi/CoreApi.xcodeproj/project.pbxproj @@ -134,6 +134,12 @@ 9974CA2D23DF197B003FE824 /* ElevationProfileData+Core.h in Headers */ = {isa = PBXBuildFile; fileRef = 9974CA2B23DF197B003FE824 /* ElevationProfileData+Core.h */; }; 999D3A64237B097C00C5F7A8 /* DeepLinkSubscriptionData.h in Headers */ = {isa = PBXBuildFile; fileRef = 999D3A62237B097C00C5F7A8 /* DeepLinkSubscriptionData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 999D3A65237B097C00C5F7A8 /* DeepLinkSubscriptionData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 999D3A63237B097C00C5F7A8 /* DeepLinkSubscriptionData.mm */; }; + 99D934A624C9FFE7002E4802 /* IMWMTrialEligibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 99D934A424C9FFE6002E4802 /* IMWMTrialEligibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 99D934A724C9FFE7002E4802 /* IMWMPurchaseValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 99D934A524C9FFE7002E4802 /* IMWMPurchaseValidation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 99D934B224CA0052002E4802 /* MWMTrialEligibility.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99D934AE24CA0051002E4802 /* MWMTrialEligibility.mm */; }; + 99D934B324CA0052002E4802 /* MWMTrialEligibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 99D934AF24CA0051002E4802 /* MWMTrialEligibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 99D934B424CA0052002E4802 /* MWMPurchaseValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99D934B024CA0052002E4802 /* MWMPurchaseValidation.mm */; }; + 99D934B524CA0052002E4802 /* MWMPurchaseValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 99D934B124CA0052002E4802 /* MWMPurchaseValidation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 99F31EB823D5DD9000CE2CE1 /* PromoAfterBookingData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99F31EB523D5DD8F00CE2CE1 /* PromoAfterBookingData.mm */; }; 99F31EB923D5DD9000CE2CE1 /* PromoAfterBookingData+Core.h in Headers */ = {isa = PBXBuildFile; fileRef = 99F31EB623D5DD8F00CE2CE1 /* PromoAfterBookingData+Core.h */; }; 99F31EBA23D5DD9000CE2CE1 /* PromoAfterBookingData.h in Headers */ = {isa = PBXBuildFile; fileRef = 99F31EB723D5DD9000CE2CE1 /* PromoAfterBookingData.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -276,6 +282,12 @@ 9974CA2B23DF197B003FE824 /* ElevationProfileData+Core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElevationProfileData+Core.h"; sourceTree = ""; }; 999D3A62237B097C00C5F7A8 /* DeepLinkSubscriptionData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeepLinkSubscriptionData.h; sourceTree = ""; }; 999D3A63237B097C00C5F7A8 /* DeepLinkSubscriptionData.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DeepLinkSubscriptionData.mm; sourceTree = ""; }; + 99D934A424C9FFE6002E4802 /* IMWMTrialEligibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IMWMTrialEligibility.h; sourceTree = ""; }; + 99D934A524C9FFE7002E4802 /* IMWMPurchaseValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IMWMPurchaseValidation.h; sourceTree = ""; }; + 99D934AE24CA0051002E4802 /* MWMTrialEligibility.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMTrialEligibility.mm; sourceTree = ""; }; + 99D934AF24CA0051002E4802 /* MWMTrialEligibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMTrialEligibility.h; sourceTree = ""; }; + 99D934B024CA0052002E4802 /* MWMPurchaseValidation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMPurchaseValidation.mm; sourceTree = ""; }; + 99D934B124CA0052002E4802 /* MWMPurchaseValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMPurchaseValidation.h; sourceTree = ""; }; 99F31EB523D5DD8F00CE2CE1 /* PromoAfterBookingData.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PromoAfterBookingData.mm; sourceTree = ""; }; 99F31EB623D5DD8F00CE2CE1 /* PromoAfterBookingData+Core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "PromoAfterBookingData+Core.h"; sourceTree = ""; }; 99F31EB723D5DD9000CE2CE1 /* PromoAfterBookingData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PromoAfterBookingData.h; sourceTree = ""; }; @@ -325,6 +337,7 @@ 470015F12342509C00EBF03D /* CoreApi */ = { isa = PBXGroup; children = ( + 99D934A324C9FFCE002E4802 /* InappPurchase */, 47EF73F6246E031E00D32AB8 /* GuidesGallery */, 47E8163D23B2B97A008FD836 /* User */, 47F4F1F623A333280022FD56 /* Storage */, @@ -677,6 +690,27 @@ path = ElevationProfile; sourceTree = ""; }; + 99D934A324C9FFCE002E4802 /* InappPurchase */ = { + isa = PBXGroup; + children = ( + 99D934B624CA0066002E4802 /* Impl */, + 99D934A524C9FFE7002E4802 /* IMWMPurchaseValidation.h */, + 99D934A424C9FFE6002E4802 /* IMWMTrialEligibility.h */, + ); + path = InappPurchase; + sourceTree = ""; + }; + 99D934B624CA0066002E4802 /* Impl */ = { + isa = PBXGroup; + children = ( + 99D934B124CA0052002E4802 /* MWMPurchaseValidation.h */, + 99D934B024CA0052002E4802 /* MWMPurchaseValidation.mm */, + 99D934AF24CA0051002E4802 /* MWMTrialEligibility.h */, + 99D934AE24CA0051002E4802 /* MWMTrialEligibility.mm */, + ); + path = Impl; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -693,6 +727,7 @@ 471AB99123AB931000F56D49 /* MWMMapSearchResult+Core.h in Headers */, 4738A8E0239FACE7007C0F43 /* CoreBanner.h in Headers */, 47942D6D237CC3E300DEFAE3 /* PlacePagePreviewData.h in Headers */, + 99D934B324CA0052002E4802 /* MWMTrialEligibility.h in Headers */, 9940622023EAC57900493D1A /* ElevationHeightPoint.h in Headers */, 3D40DED523ED9C7F00A0153A /* WebApi.h in Headers */, 47A65CAF235008E100DCD85F /* CoreApi-swift.h in Headers */, @@ -725,6 +760,7 @@ 479834F323426CD200724D1E /* MWMTagGroup+Convenience.h in Headers */, 47D9019923AC236100D9364C /* MWMMapUpdateInfo+Core.h in Headers */, 999D3A64237B097C00C5F7A8 /* DeepLinkSubscriptionData.h in Headers */, + 99D934A624C9FFE7002E4802 /* IMWMTrialEligibility.h in Headers */, 47942D7E237CC43000DEFAE3 /* UgcData+Core.h in Headers */, 47942D6E237CC3E800DEFAE3 /* PlacePagePreviewData+Core.h in Headers */, 4738A8E4239FB46E007C0F43 /* CoreBanner+Core.h in Headers */, @@ -737,10 +773,12 @@ 47E8163723B188D3008FD836 /* MWMStorage.h in Headers */, 47EEAFF62350CF48005CF316 /* AppInfo.h in Headers */, 47F701F3238C877C00D18E95 /* PlacePageButtonsData+Core.h in Headers */, + 99D934B524CA0052002E4802 /* MWMPurchaseValidation.h in Headers */, 471527392491EDAA00E91BBA /* MWMBookmarkColor.h in Headers */, 479F7053234FB7BC00011E2E /* MWMCatalogCommon.h in Headers */, 47942D7A237CC41A00DEFAE3 /* HotelRoom.h in Headers */, 4700160F2342579000EBF03D /* MWMTag.h in Headers */, + 99D934A724C9FFE7002E4802 /* IMWMPurchaseValidation.h in Headers */, 47F4F1F923A3336C0022FD56 /* MWMMapNodeAttributes.h in Headers */, 479F705B234FBB1100011E2E /* MWMUTM.h in Headers */, 47942D98237D675400DEFAE3 /* CatalogPromoItem+Core.h in Headers */, @@ -872,12 +910,14 @@ 47E8164123B2B98F008FD836 /* MWMUser.mm in Sources */, 471AB98E23AB925D00F56D49 /* MWMMapSearchResult.mm in Sources */, 479834EA2342697400724D1E /* MWMTag+Convenience.mm in Sources */, + 99D934B224CA0052002E4802 /* MWMTrialEligibility.mm in Sources */, 47C637D62354AEBE00E12DE0 /* MWMMapOverlayManager.mm in Sources */, 475784C32344B422008291A4 /* Framework.cpp in Sources */, 47942D8D237D634300DEFAE3 /* CatalogPromoData.mm in Sources */, 4718C4332355FC3C00640DF1 /* MWMNetworkPolicy.mm in Sources */, 472602A924092C5B00731135 /* MWMGeoUtil.mm in Sources */, 993F54F3237C5D1100545511 /* PromoAfterBookingCampaignAdapter.mm in Sources */, + 99D934B424CA0052002E4802 /* MWMPurchaseValidation.mm in Sources */, 47F701F0238C86F000D18E95 /* PlacePageButtonsData.mm in Sources */, 9940622123EAC57900493D1A /* ElevationHeightPoint.m in Sources */, 47EEAFF42350CEDB005CF316 /* AppInfo.mm in Sources */, diff --git a/iphone/CoreApi/CoreApi/CoreApi-swift.h b/iphone/CoreApi/CoreApi/CoreApi-swift.h index e2a0dd667e..1a93fdacd3 100644 --- a/iphone/CoreApi/CoreApi/CoreApi-swift.h +++ b/iphone/CoreApi/CoreApi/CoreApi-swift.h @@ -37,6 +37,7 @@ FOUNDATION_EXPORT const unsigned char CoreApiVersionString[]; #import #import #import +#import #pragma mark - Place Page diff --git a/iphone/CoreApi/CoreApi/CoreApi.h b/iphone/CoreApi/CoreApi/CoreApi.h index ac2d827338..c03a72f324 100644 --- a/iphone/CoreApi/CoreApi/CoreApi.h +++ b/iphone/CoreApi/CoreApi/CoreApi.h @@ -23,3 +23,5 @@ FOUNDATION_EXPORT const unsigned char CoreApiVersionString[]; #import #import #import +#import +#import diff --git a/iphone/Maps/Core/InappPurchase/IMWMPurchaseValidation.h b/iphone/CoreApi/CoreApi/InappPurchase/IMWMPurchaseValidation.h similarity index 93% rename from iphone/Maps/Core/InappPurchase/IMWMPurchaseValidation.h rename to iphone/CoreApi/CoreApi/InappPurchase/IMWMPurchaseValidation.h index 7d1755a323..3f0c803c7e 100644 --- a/iphone/Maps/Core/InappPurchase/IMWMPurchaseValidation.h +++ b/iphone/CoreApi/CoreApi/InappPurchase/IMWMPurchaseValidation.h @@ -1,3 +1,5 @@ +#import + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, MWMPurchaseValidationResult) { diff --git a/iphone/CoreApi/CoreApi/InappPurchase/IMWMTrialEligibility.h b/iphone/CoreApi/CoreApi/InappPurchase/IMWMTrialEligibility.h new file mode 100644 index 0000000000..2e8a264700 --- /dev/null +++ b/iphone/CoreApi/CoreApi/InappPurchase/IMWMTrialEligibility.h @@ -0,0 +1,20 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, MWMCheckTrialEligibilityResult) { + MWMCheckTrialEligibilityResultEligible, + MWMCheckTrialEligibilityResultNotEligible, + MWMCheckTrialEligibilityResultServerError, + MWMCheckTrialEligibilityResultNoReceipt +}; + +typedef void (^CheckTrialEligibilityCallback)(MWMCheckTrialEligibilityResult result); + +@protocol IMWMTrialEligibility + +- (void)checkTrialEligibility:(NSString *)serverId callback:(CheckTrialEligibilityCallback)callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/Core/InappPurchase/Impl/MWMPurchaseValidation.h b/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMPurchaseValidation.h similarity index 100% rename from iphone/Maps/Core/InappPurchase/Impl/MWMPurchaseValidation.h rename to iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMPurchaseValidation.h diff --git a/iphone/Maps/Core/InappPurchase/Impl/MWMPurchaseValidation.mm b/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMPurchaseValidation.mm similarity index 100% rename from iphone/Maps/Core/InappPurchase/Impl/MWMPurchaseValidation.mm rename to iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMPurchaseValidation.mm diff --git a/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMTrialEligibility.h b/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMTrialEligibility.h new file mode 100644 index 0000000000..7b3dc15eda --- /dev/null +++ b/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMTrialEligibility.h @@ -0,0 +1,12 @@ +#import "IMWMTrialEligibility.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MWMTrialEligibility : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithVendorId:(NSString *)vendorId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMTrialEligibility.mm b/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMTrialEligibility.mm new file mode 100644 index 0000000000..dc07bac4ed --- /dev/null +++ b/iphone/CoreApi/CoreApi/InappPurchase/Impl/MWMTrialEligibility.mm @@ -0,0 +1,67 @@ +#import "MWMTrialEligibility.h" + +#include + +static NSMutableDictionary *> *callbacks = [NSMutableDictionary dictionary]; + +@interface MWMTrialEligibility () + +@property (nonatomic, copy) NSString *vendorId; + +@end + +@implementation MWMTrialEligibility + +- (instancetype)initWithVendorId:(NSString *)vendorId { + self = [super init]; + if (self) { + _vendorId = vendorId; + } + + return self; +} + +-(void)checkTrialEligibility:(NSString *)serverId callback:(CheckTrialEligibilityCallback)callback { + NSURL *receiptUrl = [NSBundle mainBundle].appStoreReceiptURL; + NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl]; + if (!receiptData) { + if (callback) + callback(MWMCheckTrialEligibilityResultNoReceipt); + return; + } + + GetFramework().GetPurchase()->SetTrialEligibilityCallback([serverId](auto trialEligibilityCode){ + MWMCheckTrialEligibilityResult result; + switch (trialEligibilityCode) { + case Purchase::TrialEligibilityCode::Eligible: + result = MWMCheckTrialEligibilityResultEligible; + case Purchase::TrialEligibilityCode::NotEligible: + result = MWMCheckTrialEligibilityResultNotEligible; + case Purchase::TrialEligibilityCode::ServerError: + result = MWMCheckTrialEligibilityResultServerError; + } + + NSMutableArray *callbackArray = callbacks[serverId]; + [callbackArray enumerateObjectsUsingBlock:^(CheckTrialEligibilityCallback _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + obj(result); + }]; + + [callbacks removeObjectForKey:serverId]; + }); + + + NSMutableArray *callbackArray = callbacks[serverId]; + if (!callbackArray) { + callbackArray = [NSMutableArray arrayWithObject:[callback copy]]; + callbacks[serverId] = callbackArray; + Purchase::ValidationInfo vi; + vi.m_receiptData = [receiptData base64EncodedStringWithOptions:0].UTF8String; + vi.m_serverId = serverId.UTF8String; + vi.m_vendorId = self.vendorId.UTF8String; + GetFramework().GetPurchase()->CheckTrialEligibility(vi); + } else { + [callbackArray addObject:[callback copy]]; + } +} + +@end diff --git a/iphone/Maps/Bridging-Header.h b/iphone/Maps/Bridging-Header.h index 5766ababcd..92d055e84e 100644 --- a/iphone/Maps/Bridging-Header.h +++ b/iphone/Maps/Bridging-Header.h @@ -62,7 +62,6 @@ #import "MWMNoteCell.h" #import "MWMPlacePageManagerHelper.h" #import "MWMPurchaseManager.h" -#import "MWMPurchaseValidation.h" #import "MWMPushNotifications.h" #import "MWMRouteManagerPointType.h" #import "MWMRoutePreviewTaxiCellType.h" diff --git a/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.h b/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.h index 0dc0386c4f..bcc76a5d85 100644 --- a/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.h +++ b/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.h @@ -10,7 +10,15 @@ typedef NS_ENUM(NSUInteger, MWMValidationResult) MWMValidationResultAuthError }; +typedef NS_ENUM(NSUInteger, MWMTrialEligibilityResult) +{ + MWMTrialEligibilityResultEligible, + MWMTrialEligibilityResultNotEligible, + MWMTrialEligibilityResultServerError +}; + typedef void (^ValidateReceiptCallback)(NSString * serverId, MWMValidationResult validationResult); +typedef void (^TrialEligibilityCallback)(NSString * serverId, MWMTrialEligibilityResult result); typedef void (^StartTransactionCallback)(BOOL success, NSString * serverId); @@ -35,6 +43,9 @@ typedef void (^StartTransactionCallback)(BOOL success, NSString * serverId); - (void)validateReceipt:(NSString *)serverId refreshReceipt:(BOOL)refresh callback:(ValidateReceiptCallback)callback; +- (void)checkTrialEligibility:(NSString *)serverId + refreshReceipt:(BOOL)refresh + callback:(TrialEligibilityCallback)callback; - (void)startTransaction:(NSString *)serverId callback:(StartTransactionCallback)callback; - (void)refreshReceipt; diff --git a/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.mm b/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.mm index 903afc610c..988b786797 100644 --- a/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.mm +++ b/iphone/Maps/Core/Subscriptions/MWMPurchaseManager.mm @@ -1,17 +1,17 @@ #import "MWMPurchaseManager.h" -#import "MWMPurchaseValidation.h" -#include +#include #import @interface MWMPurchaseManager() -@property(nonatomic, copy) ValidateReceiptCallback callback; +@property(nonatomic) NSMutableDictionary *> *validationCallbacks; +@property(nonatomic) NSMutableDictionary *> *trialCallbacks; @property(nonatomic) SKReceiptRefreshRequest *receiptRequest; -@property(nonatomic, copy) NSString *serverId; @property(nonatomic, copy) NSString *vendorId; @property(nonatomic) id purchaseValidation; +@property(nonatomic) id trialEligibility; @end @@ -91,6 +91,9 @@ if (self) { _vendorId = vendorId; _purchaseValidation = [[MWMPurchaseValidation alloc] initWithVendorId:vendorId]; + _trialEligibility = [[MWMTrialEligibility alloc] initWithVendorId:vendorId]; + _validationCallbacks = [NSMutableDictionary dictionary]; + _trialCallbacks = [NSMutableDictionary dictionary]; } return self; } @@ -106,39 +109,81 @@ refreshReceipt:(BOOL)refresh callback:(ValidateReceiptCallback)callback { - self.callback = callback; - self.serverId = serverId; - [self validateReceipt:refresh]; + NSMutableArray *callbackArray = self.validationCallbacks[serverId]; + if (callbackArray) { + [callbackArray addObject:[callback copy]]; + } else { + self.validationCallbacks[serverId] = [NSMutableArray arrayWithObject:[callback copy]]; + } + [self validateReceipt:serverId refreshReceipt:refresh]; } -- (void)validateReceipt:(BOOL)refresh { +- (void)validateReceipt:(NSString *)serverId + refreshReceipt:(BOOL)refresh { __weak __typeof(self) ws = self; - [self.purchaseValidation validateReceipt:self.serverId callback:^(MWMPurchaseValidationResult validationResult) { + [self.purchaseValidation validateReceipt:serverId callback:^(MWMPurchaseValidationResult validationResult) { __strong __typeof(self) self = ws; switch (validationResult) { case MWMPurchaseValidationResultValid: - [self validReceipt]; + [self notifyValidation:serverId result:MWMValidationResultValid]; break; case MWMPurchaseValidationResultNotValid: - [self invalidReceipt]; + [self notifyValidation:serverId result:MWMValidationResultNotValid]; break; case MWMPurchaseValidationResultError: - [self serverError]; + [self notifyValidation:serverId result:MWMValidationResultServerError]; break; case MWMPurchaseValidationResultAuthError: - [self authError]; + [self notifyValidation:serverId result:MWMValidationResultAuthError]; break; case MWMPurchaseValidationResultNoReceipt: if (refresh) { [self refreshReceipt]; } else { - [self noReceipt]; + [self notifyValidation:serverId result:MWMValidationResultNotValid]; } break; } }]; } +- (void)checkTrialEligibility:(NSString *)serverId + refreshReceipt:(BOOL)refresh + callback:(TrialEligibilityCallback)callback { + NSMutableArray *callbackArray = self.trialCallbacks[serverId]; + if (callbackArray) { + [callbackArray addObject:[callback copy]]; + } else { + self.trialCallbacks[serverId] = [NSMutableArray arrayWithObject:[callback copy]]; + } + [self checkTrialEligibility:serverId refreshReceipt:refresh]; +} + +- (void)checkTrialEligibility:(NSString *)serverId + refreshReceipt:(BOOL)refresh { + __weak __typeof(self) ws = self; + [self.trialEligibility checkTrialEligibility:serverId callback:^(MWMCheckTrialEligibilityResult result) { + __strong __typeof(self) self = ws; + switch (result) { + case MWMCheckTrialEligibilityResultEligible: + [self notifyTrialEligibility:serverId result:MWMTrialEligibilityResultEligible]; + break; + case MWMCheckTrialEligibilityResultNotEligible: + [self notifyTrialEligibility:serverId result:MWMTrialEligibilityResultNotEligible]; + break; + case MWMCheckTrialEligibilityResultServerError: + [self notifyTrialEligibility:serverId result:MWMTrialEligibilityResultServerError]; + break; + case MWMCheckTrialEligibilityResultNoReceipt: + if (refresh) { + [self refreshReceipt]; + } else { + [self notifyTrialEligibility:serverId result:MWMTrialEligibilityResultNotEligible]; + } + } + }]; +} + - (void)startTransaction:(NSString *)serverId callback:(StartTransactionCallback)callback { GetFramework().GetPurchase()->SetStartTransactionCallback([callback](bool success, std::string const & serverId, @@ -151,40 +196,20 @@ GetFramework().GetUser().GetAccessToken()); } -- (void)validReceipt -{ - if (self.callback) - self.callback(self.serverId, MWMValidationResultValid); +- (void)notifyValidation:(NSString *)serverId result:(MWMValidationResult)result { + NSMutableArray *callbackArray = self.validationCallbacks[serverId]; + [callbackArray enumerateObjectsUsingBlock:^(ValidateReceiptCallback _Nonnull callback, NSUInteger idx, BOOL * _Nonnull stop) { + callback(serverId, result); + }]; + [self.validationCallbacks removeObjectForKey:serverId]; } -- (void)noReceipt -{ - if (self.callback) - self.callback(self.serverId, MWMValidationResultNotValid); -} - -- (void)invalidReceipt -{ - if (self.callback) - self.callback(self.serverId, MWMValidationResultNotValid); -} - -- (void)serverError -{ - if (self.callback) - self.callback(self.serverId, MWMValidationResultServerError); -} - -- (void)authError -{ - if (self.callback) - self.callback(self.serverId, MWMValidationResultAuthError); -} - -- (void)appstoreError:(NSError *)error -{ - if (self.callback) - self.callback(self.serverId, MWMValidationResultServerError); +-(void)notifyTrialEligibility:(NSString *)serverId result:(MWMTrialEligibilityResult)result { + NSMutableArray *callbackArray = self.trialCallbacks[serverId]; + [callbackArray enumerateObjectsUsingBlock:^(TrialEligibilityCallback _Nonnull callback, NSUInteger idx, BOOL * _Nonnull stop) { + callback(serverId, result); + }]; + [self.trialCallbacks removeObjectForKey:serverId]; } + (void)setAdsDisabled:(BOOL)disabled @@ -204,12 +229,22 @@ - (void)requestDidFinish:(SKRequest *)request { - [self validateReceipt:NO]; + [self.validationCallbacks enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) { + [self validateReceipt:key refreshReceipt:NO]; + }]; + [self.trialCallbacks enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) { + [self checkTrialEligibility:key refreshReceipt:NO]; + }]; } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { - [self appstoreError:error]; + [self.trialCallbacks enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) { + [self notifyValidation:key result:MWMValidationResultServerError]; + }]; + [self.trialCallbacks enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSMutableArray * _Nonnull obj, BOOL * _Nonnull stop) { + [self notifyTrialEligibility:key result:MWMTrialEligibilityResultServerError]; + }]; } @end diff --git a/iphone/Maps/Core/Subscriptions/SubscriptionManager.swift b/iphone/Maps/Core/Subscriptions/SubscriptionManager.swift index 8711e5e482..804a1b383d 100644 --- a/iphone/Maps/Core/Subscriptions/SubscriptionManager.swift +++ b/iphone/Maps/Core/Subscriptions/SubscriptionManager.swift @@ -1,10 +1,12 @@ @objc protocol ISubscriptionManager: class{ typealias SuscriptionsCompletion = ([ISubscription]?, Error?) -> Void typealias ValidationCompletion = (MWMValidationResult) -> Void + typealias TrialEligibilityCompletion = (MWMTrialEligibilityResult) -> Void var productIds: [String] { get } var serverId: String { get } var vendorId: String { get } + var hasTrial: Bool { get } @objc static func canMakePayments() -> Bool @objc func getAvailableSubscriptions(_ completion: @escaping SuscriptionsCompletion) @@ -12,6 +14,7 @@ @objc func addListener(_ listener: SubscriptionManagerListener) @objc func removeListener(_ listener: SubscriptionManagerListener) @objc func validate(completion: ValidationCompletion?) + @objc func checkTrialEligibility(completion: TrialEligibilityCompletion?) @objc func restore(_ callback: @escaping ValidationCompletion) @objc func setSubscriptionActive(_ value: Bool) } @@ -36,12 +39,14 @@ class SubscriptionManager: NSObject, ISubscriptionManager { let productIds: [String] let serverId: String let vendorId: String + let hasTrial: Bool private var purchaseManager: MWMPurchaseManager? init(productIds: [String], serverId: String, vendorId: String) { self.productIds = productIds self.serverId = serverId self.vendorId = vendorId + self.hasTrial = serverId == MWMPurchaseManager.allPassSubscriptionServerId() super.init() paymentQueue.add(self) self.purchaseManager = MWMPurchaseManager(vendorId: vendorId) @@ -115,6 +120,12 @@ class SubscriptionManager: NSObject, ISubscriptionManager { } } + @objc func checkTrialEligibility(completion: TrialEligibilityCompletion?) { + purchaseManager?.checkTrialEligibility(serverId, refreshReceipt: true, callback: { (_, result) in + completion?(result) + }) + } + private func logEvents(_ validationResult: MWMValidationResult) { switch validationResult { case .valid: diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index b5185bdcd1..d4e5f78da3 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -343,7 +343,6 @@ 472E3F472146BCD30020E412 /* SubscriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472E3F462146BCD30020E412 /* SubscriptionManager.swift */; }; 472E3F4A2146C4CD0020E412 /* MWMPurchaseManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 472E3F492146C4CD0020E412 /* MWMPurchaseManager.mm */; }; 472E3F4C2147D5700020E412 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472E3F4B2147D5700020E412 /* Subscription.swift */; }; - 473464A7218B0BC000D6AF5B /* MWMPurchaseValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 473464A6218B0BC000D6AF5B /* MWMPurchaseValidation.mm */; }; 4735008A23A83CF700661A95 /* DownloadedMapsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4735008923A83CF700661A95 /* DownloadedMapsDataSource.swift */; }; 4738A8E7239FC513007C0F43 /* AdBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4738A8E6239FC513007C0F43 /* AdBannerView.swift */; }; 4738A8E9239FC526007C0F43 /* AdBannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4738A8E8239FC526007C0F43 /* AdBannerView.xib */; }; @@ -1472,8 +1471,6 @@ 472E3F482146C4CD0020E412 /* MWMPurchaseManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMPurchaseManager.h; sourceTree = ""; }; 472E3F492146C4CD0020E412 /* MWMPurchaseManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMPurchaseManager.mm; sourceTree = ""; }; 472E3F4B2147D5700020E412 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; - 473464A5218B0BC000D6AF5B /* MWMPurchaseValidation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMPurchaseValidation.h; sourceTree = ""; }; - 473464A6218B0BC000D6AF5B /* MWMPurchaseValidation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMPurchaseValidation.mm; sourceTree = ""; }; 4735008923A83CF700661A95 /* DownloadedMapsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadedMapsDataSource.swift; sourceTree = ""; }; 473500C023A8F81800661A95 /* MWMFrameworkObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMFrameworkObserver.h; sourceTree = ""; }; 47375E562420ECA800FFCC49 /* Chart.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Chart.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1548,7 +1545,6 @@ 47B9065121C7FA400079C85E /* IMWMImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IMWMImageCache.h; sourceTree = ""; }; 47C7F9722191E15A00C2760C /* InAppBilling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppBilling.swift; sourceTree = ""; }; 47C7F97421930F5300C2760C /* IInAppBilling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IInAppBilling.swift; sourceTree = ""; }; - 47C7F976219310D800C2760C /* IMWMPurchaseValidation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IMWMPurchaseValidation.h; sourceTree = ""; }; 47C8788E22DF525A00A772DA /* SubscriptionSuccessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSuccessViewController.swift; sourceTree = ""; }; 47C8788F22DF525A00A772DA /* SubscriptionSuccessViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SubscriptionSuccessViewController.xib; sourceTree = ""; }; 47C8789722DF622400A772DA /* SubscriptionFailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFailViewController.swift; sourceTree = ""; }; @@ -3463,7 +3459,6 @@ 47C7F97421930F5300C2760C /* IInAppBilling.swift */, 477D7AC6218F1515007EE2CB /* IPaidRoutePurchase.swift */, 4719A644219CBD65009F9AA7 /* IPendingTransactionsHandler.swift */, - 47C7F976219310D800C2760C /* IMWMPurchaseValidation.h */, 4716EAB921A325310029B886 /* IPaidRouteStatistics.swift */, ); path = InappPurchase; @@ -3476,8 +3471,6 @@ 47C7F9722191E15A00C2760C /* InAppBilling.swift */, 470F5A7C2189BB2F00754295 /* PaidRoutePurchase.swift */, 47D0026621999DA900F651A2 /* PendingTransactionsHandler.swift */, - 473464A5218B0BC000D6AF5B /* MWMPurchaseValidation.h */, - 473464A6218B0BC000D6AF5B /* MWMPurchaseValidation.mm */, 4719A64D21A30C3B009F9AA7 /* PaidRouteStatistics.swift */, ); path = Impl; @@ -5446,7 +5439,6 @@ F63AF50F1EA6215100A1DB98 /* FilterPriceCategoryCell.swift in Sources */, 993DF12123F6BDB100AC231A /* UIViewControllerRenderer.swift in Sources */, 34D3AFF61E37A36A004100F9 /* UICollectionView+Cells.swift in Sources */, - 473464A7218B0BC000D6AF5B /* MWMPurchaseValidation.mm in Sources */, 4767CDA420AAF66B00BD8166 /* NSAttributedString+HTML.swift in Sources */, 47B06DFE21B965950094CCAD /* Geo.swift in Sources */, 6741A9A91BF340DE002C974C /* MWMDefaultAlert.mm in Sources */, diff --git a/iphone/Maps/UI/Subscription/SubscriptionInteractor.swift b/iphone/Maps/UI/Subscription/SubscriptionInteractor.swift index a9600bfc40..df3d34de44 100644 --- a/iphone/Maps/UI/Subscription/SubscriptionInteractor.swift +++ b/iphone/Maps/UI/Subscription/SubscriptionInteractor.swift @@ -1,7 +1,6 @@ protocol SubscriptionInteractorProtocol: AnyObject { func purchase(anchor: UIView, subscription: ISubscription) func restore(anchor: UIView) - func trial(anchor: UIView) } class SubscriptionInteractor { @@ -42,16 +41,10 @@ extension SubscriptionInteractor: SubscriptionInteractorProtocol { self?.subscriptionManager.subscribe(to: subscription) } } - Statistics.logEvent(kStatInappSelect, withParameters: [kStatPurchase: subscriptionManager.serverId, - kStatProduct: subscription.productId], - with: .realtime) - Statistics.logEvent(kStatInappPay, withParameters: [kStatPurchase: subscriptionManager.serverId], - with: .realtime) } func restore(anchor: UIView) { subscriptionManager.addListener(self) - Statistics.logEvent(kStatInappRestore, withParameters: [kStatPurchase: subscriptionManager.serverId]) viewController?.signup(anchor: anchor, source: .subscription) { [weak self] success in guard success else { return } self?.presenter.isLoadingHidden = false @@ -72,20 +65,6 @@ extension SubscriptionInteractor: SubscriptionInteractorProtocol { } } } - - func trial(anchor: UIView) { - subscriptionManager.addListener(self) - viewController?.signup(anchor: anchor, source: .subscription) { [weak self] success in - guard success else { return } - MWMAlertViewController.activeAlert().presentDefaultAlert(withTitle: L("trial_error_dialog"), - message: nil, - rightButtonTitle: L("ok"), - leftButtonTitle: nil) { - self?.presenter.debugTrial = false - self?.presenter.configure() - } - } - } } extension SubscriptionInteractor: SubscriptionManagerListener { diff --git a/iphone/Maps/UI/Subscription/SubscriptionPresenter.swift b/iphone/Maps/UI/Subscription/SubscriptionPresenter.swift index f5d06df504..a628c3318e 100644 --- a/iphone/Maps/UI/Subscription/SubscriptionPresenter.swift +++ b/iphone/Maps/UI/Subscription/SubscriptionPresenter.swift @@ -1,7 +1,5 @@ protocol SubscriptionPresenterProtocol: AnyObject { var isLoadingHidden: Bool { get set } - // TODO: (boriskov) remove stub - var debugTrial: Bool { get set } func configure() func purchase(anchor: UIView, period: SubscriptionPeriod) func restore(anchor: UIView) @@ -35,6 +33,28 @@ class SubscriptionPresenter { self.source = source debugTrial = subscriptionManager === InAppPurchase.allPassSubscriptionManager } + + private func configureTrial() { + guard let trialSubscriptionItem = self.subscriptionGroup?[.year] else { + fatalError() + } + view?.setModel(SubscriptionViewModel.trial(SubscriptionViewModel.TrialData(price: trialSubscriptionItem.formattedPrice))) + } + + private func configureSubscriptions() { + var data: [SubscriptionViewModel.SubscriptionData] = [] + for period in [SubscriptionPeriod.month, SubscriptionPeriod.year] { + guard let subscriptionItem = self.subscriptionGroup?[period] else { + fatalError() + } + data.append(SubscriptionViewModel.SubscriptionData(price: subscriptionItem.formattedPrice, + title: subscriptionItem.title, + period: period, + hasDiscount: subscriptionItem.hasDiscount, + discount: L("all_pass_screen_best_value"))) + } + view?.setModel(SubscriptionViewModel.subsctiption(data)) + } } extension SubscriptionPresenter: SubscriptionPresenterProtocol { @@ -69,26 +89,24 @@ extension SubscriptionPresenter: SubscriptionPresenterProtocol { let group = SubscriptionGroup(subscriptions: subscriptions) self?.subscriptionGroup = group - - if self!.debugTrial { - guard let trialSubscriptionItem = group[.year] else { - return - } - self?.view?.setModel(SubscriptionViewModel.trial(SubscriptionViewModel.TrialData(price: trialSubscriptionItem.formattedPrice))) - } else { - var data: [SubscriptionViewModel.SubscriptionData] = [] - for period in [SubscriptionPeriod.month, SubscriptionPeriod.year] { - guard let subscriptionItem = group[period] else { - assertionFailure() - return + + if self?.subscriptionManager.hasTrial == true { + self?.subscriptionManager.checkTrialEligibility { (result) in + switch result { + case .eligible: + self?.configureTrial() + case .notEligible: + self?.configureSubscriptions() + case .serverError: + MWMAlertViewController.activeAlert().presentInfoAlert(L("error_server_title"), + text: L("error_server_message")) + self?.onCancel() + @unknown default: + fatalError() } - data.append(SubscriptionViewModel.SubscriptionData(price: subscriptionItem.formattedPrice, - title: subscriptionItem.title, - period: period, - hasDiscount: subscriptionItem.hasDiscount, - discount: L("all_pass_screen_best_value"))) } - self?.view?.setModel(SubscriptionViewModel.subsctiption(data)) + } else { + self?.configureSubscriptions() } } @@ -125,10 +143,14 @@ extension SubscriptionPresenter: SubscriptionPresenterProtocol { func restore(anchor: UIView) { interactor.restore(anchor: anchor) + Statistics.logEvent(kStatInappRestore, withParameters: [kStatPurchase: subscriptionManager.serverId]) } func trial(anchor: UIView) { - interactor.trial(anchor: anchor) + guard let subscription = subscriptionGroup?[.year]?.subscription else { + return + } + interactor.purchase(anchor: anchor, subscription: subscription) } func onSubscribe() {