删除storkit 1
This commit is contained in:
@@ -132,10 +132,6 @@
|
||||
0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */; };
|
||||
0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */; };
|
||||
0498BDE42EEA885D006CC1D5 /* KBShopThemeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */; };
|
||||
0498BDF02EEC0E56006CC1D5 /* FGIAPManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE72EEC0E56006CC1D5 /* FGIAPManager.m */; };
|
||||
0498BDF12EEC0E56006CC1D5 /* FGIAPProductsFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE92EEC0E56006CC1D5 /* FGIAPProductsFilter.m */; };
|
||||
0498BDF22EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDEF2EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.m */; };
|
||||
0498BDF32EEC0E56006CC1D5 /* FGIAPService.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDEB2EEC0E56006CC1D5 /* FGIAPService.m */; };
|
||||
0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */ = {isa = PBXBuildFile; fileRef = 0498BDF42EEC50EE006CC1D5 /* emoji_categories.json */; };
|
||||
049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */; };
|
||||
049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20D2EC1CD2800FAB05D /* KBAlert.m */; };
|
||||
@@ -449,16 +445,6 @@
|
||||
0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopStyleModel.m; sourceTree = "<group>"; };
|
||||
0498BDE22EEA885D006CC1D5 /* KBShopThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopThemeModel.h; sourceTree = "<group>"; };
|
||||
0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopThemeModel.m; sourceTree = "<group>"; };
|
||||
0498BDE62EEC0E56006CC1D5 /* FGIAPManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FGIAPManager.h; sourceTree = "<group>"; };
|
||||
0498BDE72EEC0E56006CC1D5 /* FGIAPManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FGIAPManager.m; sourceTree = "<group>"; };
|
||||
0498BDE82EEC0E56006CC1D5 /* FGIAPProductsFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FGIAPProductsFilter.h; sourceTree = "<group>"; };
|
||||
0498BDE92EEC0E56006CC1D5 /* FGIAPProductsFilter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FGIAPProductsFilter.m; sourceTree = "<group>"; };
|
||||
0498BDEA2EEC0E56006CC1D5 /* FGIAPService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FGIAPService.h; sourceTree = "<group>"; };
|
||||
0498BDEB2EEC0E56006CC1D5 /* FGIAPService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FGIAPService.m; sourceTree = "<group>"; };
|
||||
0498BDEC2EEC0E56006CC1D5 /* FGIAPServiceUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FGIAPServiceUtility.h; sourceTree = "<group>"; };
|
||||
0498BDED2EEC0E56006CC1D5 /* FGIAPVerifyTransaction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FGIAPVerifyTransaction.h; sourceTree = "<group>"; };
|
||||
0498BDEE2EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+FGIsNullOrEmpty.h"; sourceTree = "<group>"; };
|
||||
0498BDEF2EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+FGIsNullOrEmpty.m"; sourceTree = "<group>"; };
|
||||
0498BDF42EEC50EE006CC1D5 /* emoji_categories.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = emoji_categories.json; sourceTree = "<group>"; };
|
||||
049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = "<group>"; };
|
||||
049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = "<group>"; };
|
||||
@@ -693,23 +679,6 @@
|
||||
path = VM;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
04122F6C2EC5F40800EF7AB3 /* FGIAPService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0498BDE62EEC0E56006CC1D5 /* FGIAPManager.h */,
|
||||
0498BDE72EEC0E56006CC1D5 /* FGIAPManager.m */,
|
||||
0498BDE82EEC0E56006CC1D5 /* FGIAPProductsFilter.h */,
|
||||
0498BDE92EEC0E56006CC1D5 /* FGIAPProductsFilter.m */,
|
||||
0498BDEA2EEC0E56006CC1D5 /* FGIAPService.h */,
|
||||
0498BDEB2EEC0E56006CC1D5 /* FGIAPService.m */,
|
||||
0498BDEC2EEC0E56006CC1D5 /* FGIAPServiceUtility.h */,
|
||||
0498BDED2EEC0E56006CC1D5 /* FGIAPVerifyTransaction.h */,
|
||||
0498BDEE2EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.h */,
|
||||
0498BDEF2EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.m */,
|
||||
);
|
||||
path = FGIAPService;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
04122F7D2EC5FC5500EF7AB3 /* V */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -988,7 +957,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04122F862EC6198C00EF7AB3 /* WMDragView */,
|
||||
04122F6C2EC5F40800EF7AB3 /* FGIAPService */,
|
||||
049FB2162EC20A6600FAB05D /* BMLongPressDragCellCollectionView */,
|
||||
048908D92EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout */,
|
||||
047920592EDEE1FC004E8522 /* CRBoxInputView */,
|
||||
@@ -2000,10 +1968,6 @@
|
||||
0479205F2EDEE1FC004E8522 /* CRBoxInputCellProperty.m in Sources */,
|
||||
047920602EDEE1FC004E8522 /* CRLineView.m in Sources */,
|
||||
047C65502EBCBA9E0035E841 /* KBShopVC.m in Sources */,
|
||||
0498BDF02EEC0E56006CC1D5 /* FGIAPManager.m in Sources */,
|
||||
0498BDF12EEC0E56006CC1D5 /* FGIAPProductsFilter.m in Sources */,
|
||||
0498BDF22EEC0E56006CC1D5 /* NSObject+FGIsNullOrEmpty.m in Sources */,
|
||||
0498BDF32EEC0E56006CC1D5 /* FGIAPService.m in Sources */,
|
||||
0477BE042EBC83130055D639 /* HomeMainVC.m in Sources */,
|
||||
0477BDFD2EBC6A170055D639 /* HomeHotVC.m in Sources */,
|
||||
04122FAA2EC73C0100EF7AB3 /* KBVipPayHeaderView.m in Sources */,
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
#import "KBULBridgeNotification.h" // Darwin 通知常量:用于通知扩展“UL已处理”
|
||||
#import "LSTPopView.h"
|
||||
#import "KBLoginPopView.h"
|
||||
#import "IAPVerifyTransactionObj.h"
|
||||
#import "FGIAPManager.h"
|
||||
#import "KBSexSelVC.h"
|
||||
#import "KBKeyboardPermissionManager.h"
|
||||
#import "KBVipPay.h"
|
||||
@@ -41,13 +39,6 @@
|
||||
/// 2 : 处理token问题(包括卸载重装场景下的 token 清理)
|
||||
[[KBUserSessionManager shared] bootstrapIfNeeded];
|
||||
|
||||
/// 3:如果当前已经是登录状态,则在启动时初始化一次内购服务。
|
||||
/// 对于“首次登录”的场景,会在登录成功(见 KBLoginVM)后再进行一次配置。
|
||||
if ([KBUserSessionManager shared].isLoggedIn) {
|
||||
// [[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]];
|
||||
}
|
||||
|
||||
|
||||
// 首次安装/升级:重置“完全访问”记录,避免继承旧安装遗留在 Keychain 中的状态
|
||||
static NSString *const kKBFullAccessRecordInitializedKey = @"KBFullAccessRecordInitialized";
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#import "AppleSignInManager.h"
|
||||
#import "KBNetworkManager.h"
|
||||
#import "KBAuthManager.h"
|
||||
#import "FGIAPManager.h"
|
||||
#import "IAPVerifyTransactionObj.h"
|
||||
#import "KBAPI.h"
|
||||
#import "KBUser.h"
|
||||
#import "KBMyVM.h"
|
||||
@@ -19,14 +17,6 @@
|
||||
|
||||
@implementation KBLoginVM
|
||||
|
||||
/// 统一初始化内购(只执行一次)。既可以在“启动时已登录”的场景调用,也可以在“登录成功回调”里调用。
|
||||
+ (void)kb_configureIAPIfNeeded {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
// [[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]];
|
||||
});
|
||||
}
|
||||
|
||||
+ (instancetype)shared {
|
||||
static KBLoginVM *vm; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ vm = [KBLoginVM new]; });
|
||||
return vm;
|
||||
@@ -76,7 +66,6 @@
|
||||
return;
|
||||
}
|
||||
[[KBUserSessionManager shared] handleLoginSuccessWithUser:user];
|
||||
[KBLoginVM kb_configureIAPIfNeeded];
|
||||
[self kb_syncKeyboardCharactersAfterLogin];
|
||||
if (completion) completion(YES, nil);
|
||||
// 保存登录态到共享钥匙串;供 App 与扩展共享
|
||||
@@ -113,7 +102,6 @@
|
||||
return;
|
||||
}
|
||||
[[KBUserSessionManager shared] handleLoginSuccessWithUser:user];
|
||||
[KBLoginVM kb_configureIAPIfNeeded];
|
||||
[self kb_syncKeyboardCharactersAfterLogin];
|
||||
if (completion) completion(YES, nil);
|
||||
}];
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
#import "FGIAPVerifyTransaction.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
FOUNDATION_EXPORT NSNotificationName const KBIAPDidCompletePurchaseNotification;
|
||||
|
||||
@interface IAPVerifyTransactionObj : NSObject <FGIAPVerifyTransaction>
|
||||
@interface IAPVerifyTransactionObj : NSObject
|
||||
|
||||
/// 校验票据(StoreKit 2 入口)
|
||||
/// - Parameters:
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#import "IAPVerifyTransactionObj.h"
|
||||
#import "PayVM.h"
|
||||
#import "KBAuthManager.h"
|
||||
#import "KBHUD.h"
|
||||
//#import "KBLoginSheetViewController.h"
|
||||
#import "KBBizCode.h"
|
||||
|
||||
NSNotificationName const KBIAPDidCompletePurchaseNotification = @"KBIAPDidCompletePurchaseNotification";
|
||||
@@ -69,115 +66,4 @@ NSNotificationName const KBIAPDidCompletePurchaseNotification = @"KBIAPDidComple
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - FGIAPVerifyTransaction
|
||||
|
||||
- (void)pushSuccessTradeReultToServer:(NSString *)receipt
|
||||
transaction:(SKPaymentTransaction *)transaction
|
||||
complete:(FGIAPVerifyTransactionPushCallBack)handler {
|
||||
if (![self checkLogin]) { return; }
|
||||
|
||||
KBLOG(@"receipt = %@", receipt);
|
||||
KBLOG(@"transactionId = %@, originalId = %@", transaction.transactionIdentifier,transaction.originalTransaction.transactionIdentifier);
|
||||
#if DEBUG
|
||||
// [self kb_debugLogReceipt:receipt transaction:transaction];
|
||||
#endif
|
||||
#if DEBUG
|
||||
// [self kb_debugLogReceipt:receipt transaction:transaction];
|
||||
#endif
|
||||
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[KBHUD showWithStatus:@"请稍等..."];
|
||||
[self verifyReceipt:receipt completion:^(BOOL success, NSString * _Nullable message, NSInteger statusCode) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
(void)self;
|
||||
if (success) {
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
[KBHUD showInfo:@"Success"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:KBIAPDidCompletePurchaseNotification object:nil];
|
||||
if (handler) handler(KBLocalized(@"Success"), nil);
|
||||
} else if (statusCode == KBBizCodeReceiptError) {
|
||||
[KBHUD dismiss];
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
} else {
|
||||
[KBHUD showError:message ?: KBLocalized(@"Failed")];
|
||||
if (handler) handler(message ?: KBLocalized(@"Failed"), nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (BOOL)checkLogin {
|
||||
BOOL loggedIn = [[KBUserSessionManager shared] isLoggedIn];
|
||||
if (!loggedIn) {
|
||||
[[KBUserSessionManager shared] goLoginVC];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
- (void)kb_debugLogReceipt:(NSString *)receipt transaction:(SKPaymentTransaction *)transaction {
|
||||
if (![receipt isKindOfClass:[NSString class]] || receipt.length == 0) {
|
||||
KBLOG(@"[IAPVerifyTransactionObj] debug receipt empty");
|
||||
return;
|
||||
}
|
||||
NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
|
||||
if (!url) { return; }
|
||||
|
||||
NSDictionary *payload = @{ @"receipt-data": receipt, @"exclude-old-transactions": @NO };
|
||||
NSError *jsonError = nil;
|
||||
NSData *body = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&jsonError];
|
||||
if (jsonError || !body) {
|
||||
KBLOG(@"[IAPVerifyTransactionObj] debug receipt json error: %@", jsonError);
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
request.HTTPMethod = @"POST";
|
||||
request.HTTPBody = body;
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
request.timeoutInterval = 30;
|
||||
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
KBLOG(@"[IAPVerifyTransactionObj] debug receipt request error: %@", error);
|
||||
return;
|
||||
}
|
||||
if (data.length == 0) {
|
||||
KBLOG(@"[IAPVerifyTransactionObj] debug receipt empty response");
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *parseError = nil;
|
||||
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
|
||||
if (!json || parseError) {
|
||||
KBLOG(@"[IAPVerifyTransactionObj] debug receipt parse error: %@", parseError);
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *status = json[@"status"];
|
||||
KBLOG(@"[IAPVerifyTransactionObj] receipt status: %@, env transaction: %@ / %@", status, transaction.transactionIdentifier, transaction.originalTransaction.transactionIdentifier);
|
||||
|
||||
void (^logEntries)(NSArray *entries, NSString *label) = ^(NSArray *entries, NSString *label) {
|
||||
for (NSDictionary *item in entries) {
|
||||
KBLOG(@"[IAPVerifyTransactionObj][%@] product:%@ transaction:%@ original:%@ purchase_date:%@", label, item[@"product_id"], item[@"transaction_id"], item[@"original_transaction_id"], item[@"purchase_date"]);
|
||||
}
|
||||
};
|
||||
|
||||
NSArray *latestInfos = json[@"latest_receipt_info"];
|
||||
if ([latestInfos isKindOfClass:[NSArray class]] && latestInfos.count > 0) {
|
||||
logEntries(latestInfos, @"latest_receipt_info");
|
||||
}
|
||||
|
||||
NSDictionary *receiptDict = json[@"receipt"];
|
||||
NSArray *inAppInfos = receiptDict[@"in_app"];
|
||||
if ([inAppInfos isKindOfClass:[NSArray class]] && inAppInfos.count > 0) {
|
||||
logEntries(inAppInfos, @"receipt.in_app");
|
||||
}
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// FGIAPManager.h
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2020/5/11.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FGIAPService.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FGIAPManager : NSObject
|
||||
|
||||
/// FGIAPService
|
||||
@property (nonatomic, strong, readonly) FGIAPService *iap;
|
||||
|
||||
/// Initialize
|
||||
+ (FGIAPManager *)shared;
|
||||
|
||||
/// Configure a server validation object
|
||||
/// @param verifyTransaction a server validation object
|
||||
- (void)setConfigureWith:(id<FGIAPVerifyTransaction>)verifyTransaction;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// FGIAPManager.m
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2020/5/11.
|
||||
//
|
||||
|
||||
#import "FGIAPManager.h"
|
||||
|
||||
@implementation FGIAPManager
|
||||
|
||||
+ (FGIAPManager *)shared{
|
||||
static FGIAPManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[FGIAPManager alloc] init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (void)setConfigureWith:(id<FGIAPVerifyTransaction>)verifyTransaction{
|
||||
self->_iap = [[FGIAPService alloc] initWithTransaction:verifyTransaction];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,24 +0,0 @@
|
||||
//
|
||||
// FGIAPProductsFilter.h
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2021/5/8.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void(^FGIAPManagerResponseBlock)(NSArray<SKProduct *> * products);
|
||||
|
||||
@interface FGIAPProductsFilter : NSObject
|
||||
|
||||
/// 获取苹果内购商品列表
|
||||
/// @param productIdentifiers 商品id列表
|
||||
/// @param completion completion
|
||||
- (void)requestProductsWith:(NSSet *)productIdentifiers completion:(nonnull FGIAPManagerResponseBlock)completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,41 +0,0 @@
|
||||
//
|
||||
// FGIAPProductsFilter.m
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2021/5/8.
|
||||
//
|
||||
|
||||
#import "FGIAPProductsFilter.h"
|
||||
#import "FGIAPServiceUtility.h"
|
||||
|
||||
@interface FGIAPProductsFilter ()<SKProductsRequestDelegate>
|
||||
@property (nonatomic,strong) SKProductsRequest *request;
|
||||
@property (nonatomic, copy) FGIAPManagerResponseBlock requestProductsBlock;
|
||||
@end
|
||||
|
||||
@implementation FGIAPProductsFilter
|
||||
|
||||
- (void)requestProductsWith:(NSSet *)productIdentifiers completion:(nonnull FGIAPManagerResponseBlock)completion{
|
||||
|
||||
if (productIdentifiers.count==0) {
|
||||
if (completion) completion([NSArray array]);
|
||||
return;
|
||||
}
|
||||
self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
|
||||
self.request.delegate = self;
|
||||
self.requestProductsBlock = completion;
|
||||
[self.request start];
|
||||
}
|
||||
|
||||
#pragma mark - SKProductsRequestDelegate
|
||||
|
||||
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
||||
NSArray *products = [response.products sortedArrayUsingComparator:^NSComparisonResult(SKProduct *obj1, SKProduct *obj2) {
|
||||
return obj1.price.doubleValue < obj2.price.doubleValue ? NSOrderedAscending : NSOrderedDescending;
|
||||
}];
|
||||
if(_requestProductsBlock) {
|
||||
_requestProductsBlock(products);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// FGIAPManager.h
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2020/5/7.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
#import "FGIAPVerifyTransaction.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, FGIAPManagerPurchaseRusult) {
|
||||
FGIAPManagerPurchaseRusultSuccess = 0, //内购成功
|
||||
FGIAPManagerPurchaseRusultHalfSuccess, //苹果扣款成功,但是验签接口失败了
|
||||
FGIAPManagerPurchaseRusultFail, //内购失败
|
||||
FGIAPManagerPurchaseRusultCancel //用户取消
|
||||
};
|
||||
typedef void(^FGIAPManagerBuyBlock)(NSString *message, FGIAPManagerPurchaseRusult result);
|
||||
|
||||
@interface FGIAPService : NSObject
|
||||
|
||||
/**
|
||||
* 初始化支付对象
|
||||
* @param verifyTransaction 一般得到苹果服务器返回的支付结果后,需要通过<verifyTransaction>再次向服务器进行二次确认,来保证整个支付链路闭环
|
||||
*/
|
||||
- (instancetype)initWithTransaction:(id<FGIAPVerifyTransaction>)verifyTransaction;
|
||||
|
||||
/**
|
||||
* iap支付
|
||||
* @param product 对应的商品
|
||||
* @param completion 支付回调
|
||||
*/
|
||||
- (void)buyProduct:(SKProduct *)product onCompletion:(FGIAPManagerBuyBlock)completion;
|
||||
|
||||
|
||||
/**
|
||||
* 在合适的时间处理从App Store下载页面触发的内购行为
|
||||
*/
|
||||
- (void)tryShouldAddStorePayments;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,235 +0,0 @@
|
||||
//
|
||||
// FGIAPManager.m
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2020/5/7.
|
||||
//
|
||||
|
||||
#import "FGIAPService.h"
|
||||
#import "FGIAPServiceUtility.h"
|
||||
#import "NSObject+FGIsNullOrEmpty.h"
|
||||
|
||||
static NSMutableDictionary *FGIAPServiceErrorMapsFromTransaction (SKPaymentTransaction *transaction) {
|
||||
NSMutableDictionary *errorMaps = [NSMutableDictionary dictionary];
|
||||
[errorMaps setValue:transaction.transactionIdentifier?:@"" forKey:@"transactionIdentifier"];
|
||||
[errorMaps setValue:transaction.originalTransaction.transactionIdentifier?:@"" forKey:@"originalTransaction.transactionIdentifier"];
|
||||
[errorMaps setValue:transaction.payment.applicationUsername?:@"" forKey:@"applicationUsername"];
|
||||
[errorMaps setValue:transaction.payment.productIdentifier?:@"" forKey:@"productIdentifier"];
|
||||
return errorMaps;
|
||||
}
|
||||
|
||||
@interface FGIAPService () <SKPaymentTransactionObserver, SKRequestDelegate>
|
||||
@property (nonatomic, strong) id<FGIAPVerifyTransaction> verifyTransaction;
|
||||
@property (nonatomic, copy) FGIAPManagerBuyBlock buyProductCompleteBlock;
|
||||
@property (nonatomic, strong) NSString *productIdentifier;
|
||||
@property (nonatomic, strong) SKPayment *APPStorePayment;
|
||||
@end
|
||||
|
||||
@implementation FGIAPService
|
||||
|
||||
- (instancetype)initWithTransaction:(id<FGIAPVerifyTransaction>)verifyTransaction{
|
||||
if (self = [super init]) {
|
||||
|
||||
_verifyTransaction = verifyTransaction;
|
||||
|
||||
if ([SKPaymentQueue defaultQueue]) {
|
||||
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)buyProduct:(SKProduct *)product onCompletion:(nonnull FGIAPManagerBuyBlock)completion{
|
||||
|
||||
if (![SKPaymentQueue canMakePayments]) {
|
||||
completion(@"Failed to obtain the internal purchase permission", FGIAPManagerPurchaseRusultFail);
|
||||
return;
|
||||
}
|
||||
if ([product.productIdentifier isNSStringAndNotEmpty]) {
|
||||
_productIdentifier = product.productIdentifier;
|
||||
_buyProductCompleteBlock = completion;
|
||||
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
|
||||
if ([SKPaymentQueue defaultQueue]) {
|
||||
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
||||
}
|
||||
}else{
|
||||
completion(@"The selected payment does not exist", FGIAPManagerPurchaseRusultFail);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tryShouldAddStorePayments{
|
||||
if (_APPStorePayment && [SKPaymentQueue defaultQueue]) {
|
||||
[[SKPaymentQueue defaultQueue] addPayment:self.APPStorePayment];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - SKPaymentTransactionObserver
|
||||
|
||||
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
|
||||
FGLog(@"%s %ld", __func__, [SKPaymentQueue defaultQueue].transactions.count);
|
||||
if (![[SKPaymentQueue defaultQueue].transactions isNSArrayAndNotEmpty]) {
|
||||
/// all transactions finished
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
|
||||
{
|
||||
for (SKPaymentTransaction *transaction in transactions)
|
||||
{
|
||||
switch (transaction.transactionState)
|
||||
{
|
||||
case SKPaymentTransactionStatePurchasing:
|
||||
FGLog(@"Add an payment to the list");
|
||||
break;
|
||||
case SKPaymentTransactionStatePurchased:
|
||||
FGLog(@"Customer paid successfully");
|
||||
[self completeTransaction:transaction retryWhenreceiptURLisEmpty:YES];
|
||||
break;
|
||||
case SKPaymentTransactionStateRestored:
|
||||
FGLog(@"The transaction has been recovered from the user's purchase history");
|
||||
[self completeTransaction:transaction retryWhenreceiptURLisEmpty:YES];
|
||||
break;
|
||||
case SKPaymentTransactionStateFailed:
|
||||
FGLog(@"Failure of commodity transaction");
|
||||
[self failedTransaction:transaction withError:FGIAPServiceErrorTypeNone];
|
||||
break;
|
||||
case SKPaymentTransactionStateDeferred:
|
||||
FGLog(@"Merchandise is suspended");
|
||||
break;
|
||||
default:
|
||||
///
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product API_AVAILABLE(ios(11.0)){
|
||||
BOOL shouldAddStorePayment = NO;
|
||||
if (_verifyTransaction && [_verifyTransaction respondsToSelector:@selector(paymentQueue:shouldAddStorePayment:forProduct:)]) {
|
||||
shouldAddStorePayment = [_verifyTransaction paymentQueue:queue shouldAddStorePayment:payment forProduct:product];
|
||||
}
|
||||
if (shouldAddStorePayment == NO) {
|
||||
_APPStorePayment = payment;
|
||||
}
|
||||
return shouldAddStorePayment;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - SKRequestDelegate
|
||||
|
||||
- (void)requestDidFinish:(SKRequest *)request{
|
||||
if ([request isKindOfClass:[SKReceiptRefreshRequest class]]) {
|
||||
SKReceiptRefreshRequest *RefreshRequest = (SKReceiptRefreshRequest *)request;
|
||||
SKPaymentTransaction *transaction = [RefreshRequest.receiptProperties valueForKey:@"transaction"];
|
||||
if (transaction) {
|
||||
[self completeTransaction:transaction retryWhenreceiptURLisEmpty:NO];
|
||||
}else{
|
||||
[self failedTransaction:transaction withError:FGIAPServiceErrorTypeReceiptNotExist];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)request:(SKRequest *)request didFailWithError:(nonnull NSError *)error{
|
||||
if ([request isKindOfClass:[SKReceiptRefreshRequest class]]) {
|
||||
SKReceiptRefreshRequest *RefreshRequest = (SKReceiptRefreshRequest *)request;
|
||||
SKPaymentTransaction *transaction = [RefreshRequest.receiptProperties valueForKey:@"transaction"];
|
||||
[self failedTransaction:transaction withError:FGIAPServiceErrorTypeReceiptNotExist];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - private method
|
||||
|
||||
- (void)completeTransaction:(SKPaymentTransaction *)transaction retryWhenreceiptURLisEmpty:(BOOL)retry{
|
||||
FGLog(@"%s %@ %@", __func__, transaction.transactionIdentifier, transaction.originalTransaction.transactionIdentifier);
|
||||
|
||||
if (![transaction.transactionIdentifier isNSStringAndNotEmpty]) {
|
||||
[self failedTransaction:transaction withError:FGIAPServiceErrorTypeTransactionIdentifierNotExist];
|
||||
return;
|
||||
}
|
||||
|
||||
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:receiptURL.path]) {
|
||||
|
||||
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
|
||||
NSString *receiptDataText = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
|
||||
[self checkReceipt:receiptDataText withTransaction:transaction handler:nil];
|
||||
|
||||
}else if (retry){
|
||||
|
||||
SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{@"transaction":transaction}];
|
||||
receiptRefreshRequest.delegate = self;
|
||||
[receiptRefreshRequest start];
|
||||
|
||||
}else{
|
||||
[self failedTransaction:transaction withError:FGIAPServiceErrorTypeReceiptNotExist];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)failedTransaction:(SKPaymentTransaction *)transaction withError:(FGIAPServiceErrorType)error{
|
||||
FGLog(@"%s Transaction error:%@ code:%ld", __func__, transaction.error.localizedDescription, transaction.error.code);
|
||||
|
||||
BOOL finish = error == FGIAPServiceErrorTypeNone;
|
||||
|
||||
if (finish && [SKPaymentQueue defaultQueue]) {
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
|
||||
}
|
||||
|
||||
NSMutableDictionary *logStatistics = [NSMutableDictionary dictionaryWithDictionary:FGIAPServiceErrorMapsFromTransaction(transaction)];
|
||||
if (self.verifyTransaction && [self.verifyTransaction respondsToSelector:@selector(pushServiceErrorLogStatistics:error:)]) {
|
||||
[logStatistics setValue:@(error) forKey:@"error"];
|
||||
[self.verifyTransaction pushServiceErrorLogStatistics:logStatistics error:error];
|
||||
}
|
||||
|
||||
if (_buyProductCompleteBlock) {
|
||||
FGIAPManagerPurchaseRusult result = FGIAPManagerPurchaseRusultHalfSuccess;
|
||||
if (error == FGIAPServiceErrorTypeNone) {
|
||||
result = transaction.error.code == SKErrorPaymentCancelled ? FGIAPManagerPurchaseRusultCancel : FGIAPManagerPurchaseRusultFail;
|
||||
}
|
||||
_buyProductCompleteBlock(transaction.error.localizedDescription, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)checkReceipt:(NSString *)receipt withTransaction:(SKPaymentTransaction *)transaction handler:(FGIAPVerifyTransactionBlock)handler{
|
||||
|
||||
WS(wSelf);
|
||||
if (_verifyTransaction && [_verifyTransaction respondsToSelector:@selector(pushSuccessTradeReultToServer:transaction:complete:)]) {
|
||||
[_verifyTransaction pushSuccessTradeReultToServer:receipt transaction:transaction complete:^(NSString * _Nonnull message, NSError * _Nullable requestErr) {
|
||||
|
||||
//polling verify transaction
|
||||
if (requestErr && requestErr.code != FGIAPServerOverdueErrorCode) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[wSelf checkReceipt:receipt withTransaction:transaction handler:handler];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
[wSelf finishTransaction:transaction result: FGIAPManagerPurchaseRusultSuccess message:message];
|
||||
}];
|
||||
}else{
|
||||
NSAssert(NO, @"You must configure the method: - pushSuccessTradeReultToServer:transaction:complete:");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)finishTransaction:(SKPaymentTransaction *)transaction result:(FGIAPManagerPurchaseRusult)result message:(NSString *)msg{
|
||||
|
||||
FGLog(@"%s finishTransaction:%@", __func__, transaction.transactionIdentifier);
|
||||
|
||||
if ([SKPaymentQueue defaultQueue]) {
|
||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||
}
|
||||
|
||||
if (transaction.originalTransaction == nil && [transaction.payment.productIdentifier isEqualToString:self.productIdentifier]) {
|
||||
self.productIdentifier = nil;
|
||||
if (_buyProductCompleteBlock) {
|
||||
_buyProductCompleteBlock(msg, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// FGIAPServiceUtility.h
|
||||
// Pods
|
||||
//
|
||||
// Created by FoneG on 2021/5/10.
|
||||
//
|
||||
|
||||
#ifndef FGIAPServiceUtility_h
|
||||
#define FGIAPServiceUtility_h
|
||||
|
||||
#define FGDEBUG
|
||||
|
||||
#if defined (FGDEBUG) && defined (DEBUG)
|
||||
#define FGLog(...) NSLog(__VA_ARGS__)
|
||||
#else
|
||||
#define FGLog(...)
|
||||
#endif
|
||||
|
||||
#define WS(wSelf) __weak typeof(self) wSelf = self
|
||||
#define FGIAPServerOverdueErrorCode 11000007 //预留code:订单提交无效,需要删除本地的订单
|
||||
|
||||
#endif /* FGIAPServiceUtility_h */
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// FGIAPVerifyTransaction.h
|
||||
// MaltBaby
|
||||
//
|
||||
// Created by FoneG on 2021/5/8.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, FGIAPVerifyTransactionRusult) {
|
||||
FGIAPManagerVerifyRusultCREATED = 0,
|
||||
FGIAPManagerVerifyRusultFail,
|
||||
FGIAPManagerVerifyRusultSuccess,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, FGIAPServiceErrorType) {
|
||||
FGIAPServiceErrorTypeNone,
|
||||
///Failed to validate receipt
|
||||
FGIAPServiceErrorTypeTransactionIdentifierNotExist,
|
||||
///No matching receipt data was found
|
||||
FGIAPServiceErrorTypeReceiptNotExist,
|
||||
///Failed to validate receipt
|
||||
FGIAPServiceErrorTypeVerifyTradeFail,
|
||||
};
|
||||
|
||||
typedef void(^FGIAPVerifyTransactionBlock)(NSString *message, FGIAPVerifyTransactionRusult result);
|
||||
typedef void(^FGIAPVerifyTransactionPushCallBack)(NSString *message, NSError * _Nullable result);
|
||||
|
||||
@protocol FGIAPVerifyTransaction <NSObject>
|
||||
|
||||
/// 苹果支付流程结束后,需要根据返回的票据等数据去自己的服务器校验
|
||||
/// @param receipt 票据
|
||||
/// @param transaction 支付事务
|
||||
/// @param handler 检验回调
|
||||
- (void)pushSuccessTradeReultToServer:(NSString *)receipt transaction:(SKPaymentTransaction *)transaction complete:(FGIAPVerifyTransactionPushCallBack)handler;
|
||||
|
||||
|
||||
@optional
|
||||
|
||||
/// 推送失败日志
|
||||
/// @param logStatistics 日志
|
||||
- (void)pushServiceErrorLogStatistics:(NSDictionary *)logStatistics error:(FGIAPServiceErrorType)error;
|
||||
|
||||
/**
|
||||
Promoting In-App Purchases
|
||||
|
||||
用户如果在 App下载页面点击购买你推广的内购商品,如果用户已经安装过你的 App 则会直接跳转你的App并调用上述代理方法;如果用户还没有安装你的 App 那么就会去下载你的 App,下载完成之后系统会推送一个通知,如果用户点击该通知就会跳转到你的App并且调用上面的代理方法
|
||||
|
||||
上面的代理方法返回 true 则表示跳转到你的 App,IAP 继续完成交易,如果返回 false 则表示推迟或者取消购买,实际开发中因为可能还需要用户登录自己的账号、生成订单等,一般都是返回 false,之后通过 FGIAPService 的. -tryShouldAddStorePayments 在合适的时机触发。
|
||||
*/
|
||||
- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product API_AVAILABLE(ios(11.0));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// NSObject+FGIsNullOrEmpty.h
|
||||
// FGIAPService
|
||||
//
|
||||
// Created by FoneG on 2021/5/10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (FGIsNullOrEmpty)
|
||||
|
||||
- (BOOL)isNSStringAndNotEmpty;
|
||||
- (BOOL)isNSArrayAndNotEmpty;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,55 +0,0 @@
|
||||
//
|
||||
// NSObject+FGIsNullOrEmpty.m
|
||||
// FGIAPService
|
||||
//
|
||||
// Created by FoneG on 2021/5/10.
|
||||
//
|
||||
|
||||
#import "NSObject+FGIsNullOrEmpty.h"
|
||||
|
||||
@implementation NSObject (FGIsNullOrEmpty)
|
||||
|
||||
- (BOOL)isNSStringAndNotEmpty
|
||||
{
|
||||
if (nil == self) {
|
||||
return false;
|
||||
}
|
||||
if ([self isKindOfClass:[NSString class]]) {
|
||||
NSString *str = (NSString*)self;
|
||||
str = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if ([str isEqualToString:@"<null>"]) {
|
||||
return false;
|
||||
}
|
||||
if ([str isEqualToString:@"(null)"]) {
|
||||
return false;
|
||||
}
|
||||
if (str.length == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if ([self isKindOfClass:[NSAttributedString class]]) {
|
||||
NSString *str = ((NSAttributedString *)self).string;
|
||||
str = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if (str.length > 0) {
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isNSArrayAndNotEmpty
|
||||
{
|
||||
if ([self isKindOfClass:[NSArray class]]) {
|
||||
NSArray *temp = (NSArray *)self;
|
||||
if (temp.count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user