forked from organicmaps/organicmaps
[iOS] refactor purchase validation, fix bugs
This commit is contained in:
parent
3026187949
commit
a6fd07037a
17 changed files with 204 additions and 188 deletions
|
@ -4,6 +4,7 @@ class BookmarksSubscriptionViewController: MWMViewController {
|
|||
@IBOutlet private var gradientView: GradientView!
|
||||
@IBOutlet private var scrollView: UIScrollView!
|
||||
@IBOutlet private var continueButton: UIButton!
|
||||
@IBOutlet var loadingView: UIView!
|
||||
|
||||
private let annualViewController = BookmarksSubscriptionCellViewController()
|
||||
private let monthlyViewController = BookmarksSubscriptionCellViewController()
|
||||
|
@ -102,9 +103,10 @@ class BookmarksSubscriptionViewController: MWMViewController {
|
|||
}
|
||||
|
||||
@IBAction func onContinue(_ sender: UIButton) {
|
||||
loadingView.isHidden = false
|
||||
MWMBookmarksManager.shared().ping { [weak self] (success) in
|
||||
guard success else {
|
||||
// self?.loadingView.isHidden = true
|
||||
self?.loadingView.isHidden = true
|
||||
let errorDialog = BookmarksSubscriptionFailViewController { [weak self] in
|
||||
self?.dismiss(animated: true)
|
||||
}
|
||||
|
@ -126,23 +128,36 @@ class BookmarksSubscriptionViewController: MWMViewController {
|
|||
}
|
||||
|
||||
extension BookmarksSubscriptionViewController: SubscriptionManagerListener {
|
||||
func didFailToSubscribe(_ subscription: ISubscription, error: Error?) {
|
||||
func didFailToValidate() {
|
||||
loadingView.isHidden = true
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
|
||||
func didValidate(_ isValid: Bool) {
|
||||
loadingView.isHidden = true
|
||||
if (isValid) {
|
||||
onSubscribe?()
|
||||
let successDialog = BookmarksSubscriptionSuccessViewController { [weak self] in
|
||||
self?.dismiss(animated: true)
|
||||
}
|
||||
present(successDialog, animated: true)
|
||||
} else {
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
}
|
||||
|
||||
func didFailToSubscribe(_ subscription: ISubscription, error: Error?) {
|
||||
loadingView.isHidden = true
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
|
||||
func didSubsribe(_ subscription: ISubscription) {
|
||||
onSubscribe?()
|
||||
}
|
||||
|
||||
func didFailToValidate(_ subscription: ISubscription, error: Error?) {
|
||||
|
||||
}
|
||||
|
||||
func didDefer(_ subscription: ISubscription) {
|
||||
|
||||
}
|
||||
|
||||
func validationError() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<outlet property="annualView" destination="Bae-oL-ekX" id="l9n-u0-LNW"/>
|
||||
<outlet property="continueButton" destination="neX-0h-hs4" id="kQw-7y-Igs"/>
|
||||
<outlet property="gradientView" destination="BcI-3g-iCI" id="Rmd-LB-Rl5"/>
|
||||
<outlet property="loadingView" destination="BHb-cU-Ze3" id="bfe-6a-BJf"/>
|
||||
<outlet property="monthlyView" destination="Jpy-cA-wCv" id="6yi-OC-MrD"/>
|
||||
<outlet property="scrollView" destination="q5w-jW-Chn" id="dMR-TM-gMC"/>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
|
@ -261,6 +262,24 @@
|
|||
<constraint firstItem="Eiz-QQ-h0b" firstAttribute="width" secondItem="0eE-hs-sId" secondAttribute="width" id="ke9-ke-QPG"/>
|
||||
</constraints>
|
||||
</scrollView>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="BHb-cU-Ze3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="XZg-yN-Jtv">
|
||||
<rect key="frame" x="188.66666666666666" y="349.66666666666669" width="37" height="37"/>
|
||||
<color key="color" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="0.89998929790000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="XZg-yN-Jtv" firstAttribute="centerX" secondItem="BHb-cU-Ze3" secondAttribute="centerX" id="Ade-4z-Spa"/>
|
||||
<constraint firstItem="XZg-yN-Jtv" firstAttribute="centerY" secondItem="BHb-cU-Ze3" secondAttribute="centerY" id="CQX-i5-ZcY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Nh0-0H-Zgm"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="toastBackground"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
|
@ -269,9 +288,13 @@
|
|||
<constraint firstItem="0eE-hs-sId" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="Avd-W3-cXU"/>
|
||||
<constraint firstAttribute="trailing" secondItem="BcI-3g-iCI" secondAttribute="trailing" id="IKZ-OT-jxC"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BcI-3g-iCI" secondAttribute="bottom" id="SHg-Ke-dap"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BHb-cU-Ze3" secondAttribute="bottom" id="X6W-ia-1Mi"/>
|
||||
<constraint firstAttribute="trailing" secondItem="BHb-cU-Ze3" secondAttribute="trailing" id="dNr-7E-k98"/>
|
||||
<constraint firstItem="BcI-3g-iCI" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="doL-Dw-bDS"/>
|
||||
<constraint firstItem="BHb-cU-Ze3" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="ipn-ug-1wp"/>
|
||||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="0eE-hs-sId" secondAttribute="bottom" id="ku8-zI-AI4"/>
|
||||
<constraint firstItem="0eE-hs-sId" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="sv4-ek-sAP"/>
|
||||
<constraint firstAttribute="top" secondItem="BHb-cU-Ze3" secondAttribute="top" id="vTu-oZ-veb"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
|
|
|
@ -3,6 +3,16 @@ class BookmarksLoadedViewController: UIViewController {
|
|||
@objc var onViewBlock: MWMVoidBlock?
|
||||
@objc var onCancelBlock: MWMVoidBlock?
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
transitioningDelegate = transitioning
|
||||
modalPresentationStyle = .custom
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@IBAction func onViewMap(_ sender: UIButton) {
|
||||
onViewBlock?()
|
||||
}
|
||||
|
@ -10,14 +20,4 @@ class BookmarksLoadedViewController: UIViewController {
|
|||
@IBAction func onNotNow(_ sender: UIButton) {
|
||||
onCancelBlock?()
|
||||
}
|
||||
|
||||
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
|
||||
get { return transitioning }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var modalPresentationStyle: UIModalPresentationStyle {
|
||||
get { return .custom }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ class BookmarksSubscriptionExpiredViewController: UIViewController {
|
|||
self.onSubscribe = onSubscribe
|
||||
self.onDelete = onDelete
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
transitioningDelegate = transitioning
|
||||
modalPresentationStyle = .custom
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -24,14 +26,4 @@ class BookmarksSubscriptionExpiredViewController: UIViewController {
|
|||
@IBAction func onDelete(_ sender: UIButton) {
|
||||
onDelete()
|
||||
}
|
||||
|
||||
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
|
||||
get { return transitioning }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var modalPresentationStyle: UIModalPresentationStyle {
|
||||
get { return .custom }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ class BookmarksSubscriptionFailViewController: UIViewController {
|
|||
init(onOk: @escaping MWMVoidBlock) {
|
||||
self.onOk = onOk
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
transitioningDelegate = transitioning
|
||||
modalPresentationStyle = .custom
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -18,14 +20,4 @@ class BookmarksSubscriptionFailViewController: UIViewController {
|
|||
@IBAction func onOk(_ sender: UIButton) {
|
||||
onOk()
|
||||
}
|
||||
|
||||
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
|
||||
get { return transitioning }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var modalPresentationStyle: UIModalPresentationStyle {
|
||||
get { return .custom }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ class BookmarksSubscriptionSuccessViewController: UIViewController {
|
|||
init(onOk: @escaping MWMVoidBlock) {
|
||||
self.onOk = onOk
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
transitioningDelegate = transitioning
|
||||
modalPresentationStyle = .custom
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -18,14 +20,4 @@ class BookmarksSubscriptionSuccessViewController: UIViewController {
|
|||
@IBAction func onOk(_ sender: UIButton) {
|
||||
onOk()
|
||||
}
|
||||
|
||||
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
|
||||
get { return transitioning }
|
||||
set { }
|
||||
}
|
||||
|
||||
override var modalPresentationStyle: UIModalPresentationStyle {
|
||||
get { return .custom }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,28 +204,36 @@ class PaidRouteViewController: MWMViewController {
|
|||
}
|
||||
|
||||
extension PaidRouteViewController : SubscriptionManagerListener {
|
||||
func didFailToValidate() {
|
||||
loadingView.isHidden = true
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
|
||||
func didValidate(_ isValid: Bool) {
|
||||
loadingView.isHidden = true
|
||||
if (isValid) {
|
||||
delegate?.didCompleteSubscription(self)
|
||||
let successDialog = BookmarksSubscriptionSuccessViewController { [weak self] in
|
||||
self?.dismiss(animated: true)
|
||||
}
|
||||
present(successDialog, animated: true)
|
||||
} else {
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
}
|
||||
|
||||
func didFailToSubscribe(_ subscription: ISubscription, error: Error?) {
|
||||
loadingView.isHidden = true
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
|
||||
func didSubsribe(_ subscription: ISubscription) {
|
||||
loadingView.isHidden = true
|
||||
delegate?.didCompleteSubscription(self)
|
||||
let successDialog = BookmarksSubscriptionSuccessViewController { [weak self] in
|
||||
self?.dismiss(animated: true)
|
||||
}
|
||||
present(successDialog, animated: true)
|
||||
}
|
||||
|
||||
func didFailToValidate(_ subscription: ISubscription, error: Error?) {
|
||||
loadingView.isHidden = true
|
||||
}
|
||||
|
||||
func didDefer(_ subscription: ISubscription) {
|
||||
loadingView.isHidden = true
|
||||
}
|
||||
|
||||
func validationError() {
|
||||
loadingView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -248,7 +248,10 @@ using namespace osm_auth_ios;
|
|||
[UNUserNotificationCenter currentNotificationCenter].delegate = self.notificationManager;
|
||||
|
||||
if ([MWMFrameworkHelper isWiFiConnected]) {
|
||||
[[InAppPurchase adsRemovalSubscriptionManager] validate];
|
||||
[[InAppPurchase bookmarksSubscriptionManager] validateWithCompletion:nil];
|
||||
[[InAppPurchase adsRemovalSubscriptionManager] validateWithCompletion:^(MWMValidationResult result) {
|
||||
[MWMPurchaseManager setAdsDisabled:result != MWMValidationResultNotValid];
|
||||
}];
|
||||
self.pendingTransactionHandler = [InAppPurchase pendingTransactionsHandler];
|
||||
__weak __typeof(self) ws = self;
|
||||
[self.pendingTransactionHandler handlePendingTransactions:^(PendingTransactionsStatus) {
|
||||
|
|
|
@ -5,6 +5,7 @@ typedef NS_ENUM(NSUInteger, MWMPurchaseValidationResult) {
|
|||
MWMPurchaseValidationResultNotValid,
|
||||
MWMPurchaseValidationResultError,
|
||||
MWMPurchaseValidationResultAuthError,
|
||||
MWMPurchaseValidationResultNoReceipt
|
||||
};
|
||||
|
||||
typedef void (^ValidatePurchaseCallback)(MWMPurchaseValidationResult validationResult);
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
#include "Framework.h"
|
||||
#include "private.h"
|
||||
|
||||
static NSMutableDictionary<NSString *, NSMutableArray<ValidatePurchaseCallback> *> *callbacks = [NSMutableDictionary dictionary];
|
||||
|
||||
@interface MWMPurchaseValidation ()
|
||||
|
||||
@property (nonatomic, copy) NSString *vendorId;
|
||||
@property (nonatomic, copy) ValidatePurchaseCallback callback;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -22,51 +23,54 @@
|
|||
}
|
||||
|
||||
- (void)validateReceipt:(NSString *)serverId callback:(ValidatePurchaseCallback)callback {
|
||||
self.callback = callback;
|
||||
NSURL * receiptUrl = [NSBundle mainBundle].appStoreReceiptURL;
|
||||
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
|
||||
|
||||
if (!receiptData)
|
||||
{
|
||||
[self validationComplete:MWMPurchaseValidationResultNotValid];
|
||||
NSURL *receiptUrl = [NSBundle mainBundle].appStoreReceiptURL;
|
||||
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
|
||||
if (!receiptData) {
|
||||
if (callback)
|
||||
callback(MWMPurchaseValidationResultNoReceipt);
|
||||
return;
|
||||
}
|
||||
|
||||
GetFramework().GetPurchase()->SetValidationCallback([self](auto validationCode, auto const & validationInfo) {
|
||||
GetFramework().GetPurchase()->SetValidationCallback([](auto validationCode, auto const &validationInfo) {
|
||||
MWMPurchaseValidationResult result;
|
||||
switch (validationCode) {
|
||||
case Purchase::ValidationCode::Verified:
|
||||
[self validationComplete:MWMPurchaseValidationResultValid];
|
||||
result = MWMPurchaseValidationResultValid;
|
||||
break;
|
||||
case Purchase::ValidationCode::NotVerified:
|
||||
[self validationComplete:MWMPurchaseValidationResultNotValid];
|
||||
result = MWMPurchaseValidationResultNotValid;
|
||||
break;
|
||||
case Purchase::ValidationCode::ServerError: {
|
||||
[self validationComplete:MWMPurchaseValidationResultError];
|
||||
result = MWMPurchaseValidationResultError;
|
||||
break;
|
||||
case Purchase::ValidationCode::AuthError:
|
||||
[self validationComplete:MWMPurchaseValidationResultAuthError];
|
||||
result = MWMPurchaseValidationResultAuthError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GetFramework().GetPurchase()->SetValidationCallback(nullptr);
|
||||
|
||||
NSString *serverId = @(validationInfo.m_serverId.c_str());
|
||||
NSMutableArray<ValidatePurchaseCallback> *callbackArray = callbacks[serverId];
|
||||
[callbackArray enumerateObjectsUsingBlock:^(ValidatePurchaseCallback _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
obj(result);
|
||||
}];
|
||||
|
||||
[callbacks removeObjectForKey:serverId];
|
||||
});
|
||||
|
||||
Purchase::ValidationInfo vi;
|
||||
vi.m_receiptData = [receiptData base64EncodedStringWithOptions:0].UTF8String;
|
||||
vi.m_serverId = serverId.UTF8String;
|
||||
vi.m_vendorId = self.vendorId.UTF8String;
|
||||
auto const accessToken = GetFramework().GetUser().GetAccessToken();
|
||||
GetFramework().GetPurchase()->Validate(vi, accessToken);
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)validationComplete:(MWMPurchaseValidationResult)result {
|
||||
if (self.callback)
|
||||
self.callback(result);
|
||||
|
||||
self.callback = nil;
|
||||
NSMutableArray<ValidatePurchaseCallback> *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;
|
||||
auto const accessToken = GetFramework().GetUser().GetAccessToken();
|
||||
GetFramework().GetPurchase()->Validate(vi, accessToken);
|
||||
} else {
|
||||
[callbackArray addObject:[callback copy]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -20,8 +20,10 @@ final class PaidRoutePurchase: NSObject, IPaidRoutePurchase {
|
|||
private var storeProductCompletion: StoreProductCompletion?
|
||||
private var storePaymentCompletion: StorePaymentCompletion?
|
||||
private var billingProduct: IBillingProduct?
|
||||
private var purchaseManager: MWMPurchaseManager
|
||||
|
||||
init(serverId: String,
|
||||
vendorId: String,
|
||||
productId: String,
|
||||
purchaseValidation: IMWMPurchaseValidation,
|
||||
billing: IInAppBilling) {
|
||||
|
@ -29,6 +31,7 @@ final class PaidRoutePurchase: NSObject, IPaidRoutePurchase {
|
|||
self.productId = productId
|
||||
self.purchaseValidation = purchaseValidation
|
||||
self.billing = billing
|
||||
self.purchaseManager = MWMPurchaseManager(vendorId: vendorId)
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
@ -50,7 +53,7 @@ final class PaidRoutePurchase: NSObject, IPaidRoutePurchase {
|
|||
}
|
||||
|
||||
storePaymentCompletion = completion
|
||||
MWMPurchaseManager.shared().startTransaction(serverId) { [weak self] (success, serverId) in
|
||||
purchaseManager.startTransaction(serverId) { [weak self] (success, serverId) in
|
||||
if !success {
|
||||
self?.storePaymentCompletion?(.error, RoutePurchaseError.paymentError)
|
||||
self?.storePaymentCompletion = nil
|
||||
|
@ -77,7 +80,7 @@ final class PaidRoutePurchase: NSObject, IPaidRoutePurchase {
|
|||
case .valid:
|
||||
self?.billing.finishTransaction()
|
||||
self?.storePaymentCompletion?(.success, nil)
|
||||
case .notValid:
|
||||
case .notValid, .noReceipt:
|
||||
self?.storePaymentCompletion?(.error, RoutePurchaseError.validationFailed)
|
||||
case .error:
|
||||
self?.storePaymentCompletion?(.error, RoutePurchaseError.validationError)
|
||||
|
|
|
@ -15,7 +15,7 @@ final class PendingTransactionsHandler: IPendingTransactionsHandler {
|
|||
case .paid:
|
||||
purchaseValidation.validateReceipt("") { [weak self] in
|
||||
switch $0 {
|
||||
case .valid, .notValid:
|
||||
case .valid, .notValid, .noReceipt:
|
||||
completion(.success)
|
||||
self?.pendingTransaction.finishTransaction()
|
||||
case .error:
|
||||
|
|
|
@ -5,6 +5,7 @@ final class InAppPurchase: NSObject {
|
|||
let validation = MWMPurchaseValidation(vendorId: BOOKMARKS_VENDOR)
|
||||
let billing = InAppBilling()
|
||||
return PaidRoutePurchase(serverId: serverId,
|
||||
vendorId: BOOKMARKS_VENDOR,
|
||||
productId: productId,
|
||||
purchaseValidation: validation,
|
||||
billing: billing)
|
||||
|
|
|
@ -24,14 +24,13 @@ typedef void (^StartTransactionCallback)(BOOL success, NSString * serverId);
|
|||
+ (NSArray<NSString *> *)productIds;
|
||||
+ (NSArray<NSString *> *)legacyProductIds;
|
||||
+ (NSArray<NSString *> *)bookmarkInappIds;
|
||||
+ (MWMPurchaseManager *)sharedManager;
|
||||
+ (void)setAdsDisabled:(BOOL)disabled;
|
||||
|
||||
- (instancetype)initWithVendorId:(NSString *)vendorId;
|
||||
- (void)validateReceipt:(NSString *)serverId
|
||||
vendorId:(NSString *)vendorId
|
||||
refreshReceipt:(BOOL)refresh
|
||||
callback:(ValidateReceiptCallback)callback;
|
||||
- (void)startTransaction:(NSString *)serverId callback:(StartTransactionCallback)callback;
|
||||
- (void)setAdsDisabled:(BOOL)disabled;
|
||||
- (void)refreshReceipt;
|
||||
|
||||
@end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#import "MWMPurchaseManager.h"
|
||||
#import "MWMPurchaseValidation.h"
|
||||
|
||||
#include "Framework.h"
|
||||
#include "private.h"
|
||||
|
@ -9,8 +10,9 @@
|
|||
|
||||
@property(nonatomic, copy) ValidateReceiptCallback callback;
|
||||
@property(nonatomic) SKReceiptRefreshRequest *receiptRequest;
|
||||
@property(nonatomic, copy) NSString * serverId;
|
||||
@property(nonatomic, copy) NSString * vendorId;
|
||||
@property(nonatomic, copy) NSString *serverId;
|
||||
@property(nonatomic, copy) NSString *vendorId;
|
||||
@property(nonatomic) id<IMWMPurchaseValidation> purchaseValidation;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -69,14 +71,13 @@
|
|||
return [result copy];
|
||||
}
|
||||
|
||||
+ (MWMPurchaseManager *)sharedManager
|
||||
{
|
||||
static MWMPurchaseManager *instance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[MWMPurchaseManager alloc] init];
|
||||
});
|
||||
return instance;
|
||||
- (instancetype)initWithVendorId:(NSString *)vendorId {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_vendorId = vendorId;
|
||||
_purchaseValidation = [[MWMPurchaseValidation alloc] initWithVendorId:vendorId];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)refreshReceipt
|
||||
|
@ -87,53 +88,40 @@
|
|||
}
|
||||
|
||||
- (void)validateReceipt:(NSString *)serverId
|
||||
vendorId:(NSString *)vendorId
|
||||
refreshReceipt:(BOOL)refresh
|
||||
callback:(ValidateReceiptCallback)callback
|
||||
{
|
||||
self.callback = callback;
|
||||
self.serverId = serverId;
|
||||
self.vendorId = vendorId;
|
||||
[self validateReceipt:refresh];
|
||||
}
|
||||
|
||||
- (void)validateReceipt:(BOOL)refresh
|
||||
{
|
||||
NSURL * receiptUrl = [NSBundle mainBundle].appStoreReceiptURL;
|
||||
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
|
||||
|
||||
if (!receiptData)
|
||||
{
|
||||
if (refresh)
|
||||
[self refreshReceipt];
|
||||
else
|
||||
[self noReceipt];
|
||||
return;
|
||||
}
|
||||
|
||||
GetFramework().GetPurchase()->SetValidationCallback([self](auto validationCode, auto const & validationInfo)
|
||||
{
|
||||
switch (validationCode)
|
||||
{
|
||||
case Purchase::ValidationCode::Verified:
|
||||
[self validReceipt];
|
||||
break;
|
||||
case Purchase::ValidationCode::NotVerified:
|
||||
[self invalidReceipt];
|
||||
break;
|
||||
case Purchase::ValidationCode::ServerError:
|
||||
case Purchase::ValidationCode::AuthError:
|
||||
[self serverError];
|
||||
break;
|
||||
- (void)validateReceipt:(BOOL)refresh {
|
||||
__weak __typeof(self) ws = self;
|
||||
[self.purchaseValidation validateReceipt:self.serverId callback:^(MWMPurchaseValidationResult validationResult) {
|
||||
__strong __typeof(self) self = ws;
|
||||
switch (validationResult) {
|
||||
case MWMPurchaseValidationResultValid:
|
||||
[self validReceipt];
|
||||
break;
|
||||
case MWMPurchaseValidationResultNotValid:
|
||||
[self invalidReceipt];
|
||||
break;
|
||||
case MWMPurchaseValidationResultError:
|
||||
[self serverError];
|
||||
break;
|
||||
case MWMPurchaseValidationResultAuthError:
|
||||
[self authError];
|
||||
break;
|
||||
case MWMPurchaseValidationResultNoReceipt:
|
||||
if (refresh) {
|
||||
[self refreshReceipt];
|
||||
} else {
|
||||
[self noReceipt];
|
||||
}
|
||||
break;
|
||||
}
|
||||
GetFramework().GetPurchase()->SetValidationCallback(nullptr);
|
||||
});
|
||||
Purchase::ValidationInfo vi;
|
||||
vi.m_receiptData = [receiptData base64EncodedStringWithOptions:0].UTF8String;
|
||||
vi.m_serverId = self.serverId.UTF8String;
|
||||
vi.m_vendorId = self.vendorId.UTF8String;
|
||||
auto const accessToken = GetFramework().GetUser().GetAccessToken();
|
||||
GetFramework().GetPurchase()->Validate(vi, accessToken);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startTransaction:(NSString *)serverId callback:(StartTransactionCallback)callback {
|
||||
|
@ -184,7 +172,7 @@
|
|||
self.callback(self.serverId, MWMValidationResultServerError);
|
||||
}
|
||||
|
||||
- (void)setAdsDisabled:(BOOL)disabled
|
||||
+ (void)setAdsDisabled:(BOOL)disabled
|
||||
{
|
||||
GetFramework().GetPurchase()->SetSubscriptionEnabled(SubscriptionType::RemoveAds, disabled);
|
||||
}
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
@objc protocol SubscriptionManagerListener: AnyObject {
|
||||
func didFailToSubscribe(_ subscription: ISubscription, error: Error?)
|
||||
func didSubsribe(_ subscription: ISubscription)
|
||||
func didFailToValidate(_ subscription: ISubscription, error: Error?)
|
||||
func didDefer(_ subscription: ISubscription)
|
||||
func validationError()
|
||||
func didFailToValidate()
|
||||
func didValidate(_ isValid: Bool)
|
||||
}
|
||||
|
||||
class SubscriptionManager: NSObject {
|
||||
typealias SuscriptionsCompletion = ([ISubscription]?, Error?) -> Void
|
||||
typealias RestorationCompletion = (MWMValidationResult) -> Void
|
||||
|
||||
// @objc static var shared: SubscriptionManager = { return SubscriptionManager() }()
|
||||
typealias ValidationCompletion = (MWMValidationResult) -> Void
|
||||
|
||||
private let paymentQueue = SKPaymentQueue.default()
|
||||
|
||||
private var productsRequest: SKProductsRequest?
|
||||
private var subscriptionsComplection: SuscriptionsCompletion?
|
||||
private var products: [String: SKProduct]?
|
||||
private var pendingSubscription: ISubscription?
|
||||
private var listeners = NSHashTable<SubscriptionManagerListener>.weakObjects()
|
||||
private var restorationCallback: RestorationCompletion?
|
||||
private var restorationCallback: ValidationCompletion?
|
||||
|
||||
private var productIds: [String] = []
|
||||
private var serverId: String = ""
|
||||
private var vendorId: String = ""
|
||||
private var purchaseManager: MWMPurchaseManager?
|
||||
|
||||
convenience init(productIds: [String], serverId: String, vendorId: String) {
|
||||
self.init()
|
||||
self.productIds = productIds
|
||||
self.serverId = serverId
|
||||
self.vendorId = vendorId
|
||||
self.purchaseManager = MWMPurchaseManager(vendorId: vendorId)
|
||||
}
|
||||
|
||||
override private init() {
|
||||
|
@ -66,29 +65,29 @@ class SubscriptionManager: NSObject {
|
|||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
@objc func validate() {
|
||||
validate(false)
|
||||
@objc func validate(completion: ValidationCompletion? = nil) {
|
||||
validate(false, completion: completion)
|
||||
}
|
||||
|
||||
@objc func restore(_ callback: @escaping RestorationCompletion) {
|
||||
restorationCallback = callback
|
||||
validate(true)
|
||||
@objc func restore(_ callback: @escaping ValidationCompletion) {
|
||||
validate(true) {
|
||||
callback($0)
|
||||
}
|
||||
}
|
||||
|
||||
private func validate(_ refreshReceipt: Bool) {
|
||||
MWMPurchaseManager.shared()
|
||||
.validateReceipt(serverId, vendorId: vendorId, refreshReceipt: refreshReceipt) { [weak self] (_, validationResult) in
|
||||
self?.logEvents(validationResult)
|
||||
if validationResult == .valid || validationResult == .notValid {
|
||||
MWMPurchaseManager.shared().setAdsDisabled(validationResult == .valid)
|
||||
self?.paymentQueue.transactions
|
||||
.filter { self?.productIds.contains($0.payment.productIdentifier) ?? false &&
|
||||
($0.transactionState == .purchased || $0.transactionState == .restored) }
|
||||
.forEach { self?.paymentQueue.finishTransaction($0) }
|
||||
}
|
||||
|
||||
self?.restorationCallback?(validationResult)
|
||||
self?.restorationCallback = nil
|
||||
private func validate(_ refreshReceipt: Bool, completion: ValidationCompletion? = nil) {
|
||||
purchaseManager?.validateReceipt(serverId, refreshReceipt: refreshReceipt) { [weak self] (_, validationResult) in
|
||||
self?.logEvents(validationResult)
|
||||
if validationResult == .valid || validationResult == .notValid {
|
||||
self?.listeners.allObjects.forEach { $0.didValidate(validationResult == .valid) }
|
||||
self?.paymentQueue.transactions
|
||||
.filter { self?.productIds.contains($0.payment.productIdentifier) ?? false &&
|
||||
($0.transactionState == .purchased || $0.transactionState == .restored) }
|
||||
.forEach { self?.paymentQueue.finishTransaction($0) }
|
||||
} else {
|
||||
self?.listeners.allObjects.forEach { $0.didFailToValidate() }
|
||||
}
|
||||
completion?(validationResult)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +165,6 @@ extension SubscriptionManager: SKPaymentTransactionObserver {
|
|||
}
|
||||
|
||||
private func processPurchased(_ transaction: SKPaymentTransaction) {
|
||||
MWMPurchaseManager.shared().setAdsDisabled(true)
|
||||
paymentQueue.finishTransaction(transaction)
|
||||
if let ps = pendingSubscription, transaction.payment.productIdentifier == ps.productId {
|
||||
Statistics.logEvent(kStatInappPaymentSuccess)
|
||||
|
@ -175,7 +173,6 @@ extension SubscriptionManager: SKPaymentTransactionObserver {
|
|||
}
|
||||
|
||||
private func processRestored(_ transaction: SKPaymentTransaction) {
|
||||
MWMPurchaseManager.shared().setAdsDisabled(true)
|
||||
paymentQueue.finishTransaction(transaction)
|
||||
if let ps = pendingSubscription, transaction.payment.productIdentifier == ps.productId {
|
||||
listeners.allObjects.forEach { $0.didSubsribe(ps) }
|
||||
|
|
|
@ -219,22 +219,20 @@ import SafariServices
|
|||
}
|
||||
|
||||
extension RemoveAdsViewController: SubscriptionManagerListener {
|
||||
func validationError() {
|
||||
hidePurchaseProgress()
|
||||
delegate?.didCompleteSubscribtion(self)
|
||||
func didFailToValidate() {
|
||||
|
||||
}
|
||||
|
||||
func didValidate(_ isValid: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func didSubsribe(_ subscription: ISubscription) {
|
||||
MWMPurchaseManager.setAdsDisabled(true)
|
||||
hidePurchaseProgress()
|
||||
delegate?.didCompleteSubscribtion(self)
|
||||
}
|
||||
|
||||
func didFailToValidate(_ subscription: ISubscription, error: Error?) {
|
||||
hidePurchaseProgress()
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_convert_error_title"),
|
||||
text: L("purchase_error_subtitle"))
|
||||
}
|
||||
|
||||
func didDefer(_ subscription: ISubscription) {
|
||||
hidePurchaseProgress()
|
||||
delegate?.didCompleteSubscribtion(self)
|
||||
|
|
Loading…
Add table
Reference in a new issue