处理storkit2

This commit is contained in:
2025-12-16 13:49:08 +08:00
parent fd0ddfd45a
commit 1651258eec
9 changed files with 245 additions and 75 deletions

View File

@@ -46,6 +46,7 @@
0450AC152EF11E4400B6AF06 /* TransactionHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AC042EF11E4400B6AF06 /* TransactionHistory.swift */; }; 0450AC152EF11E4400B6AF06 /* TransactionHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AC042EF11E4400B6AF06 /* TransactionHistory.swift */; };
0450AC162EF11E4400B6AF06 /* StoreKitServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450ABFD2EF11E4400B6AF06 /* StoreKitServiceDelegate.swift */; }; 0450AC162EF11E4400B6AF06 /* StoreKitServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450ABFD2EF11E4400B6AF06 /* StoreKitServiceDelegate.swift */; };
0450AC172EF11E4400B6AF06 /* StoreKitState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AC032EF11E4400B6AF06 /* StoreKitState.swift */; }; 0450AC172EF11E4400B6AF06 /* StoreKitState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AC032EF11E4400B6AF06 /* StoreKitState.swift */; };
0450AC1B2EF11E4400B6AF06 /* KBStoreKitBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AC1A2EF11E4400B6AF06 /* KBStoreKitBridge.swift */; };
0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; }; 0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; };
0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; 0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; };
0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; 0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; };
@@ -297,6 +298,7 @@
0450AC042EF11E4400B6AF06 /* TransactionHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistory.swift; sourceTree = "<group>"; }; 0450AC042EF11E4400B6AF06 /* TransactionHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistory.swift; sourceTree = "<group>"; };
0450AC062EF11E4400B6AF06 /* StoreKitDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitDelegate.swift; sourceTree = "<group>"; }; 0450AC062EF11E4400B6AF06 /* StoreKitDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitDelegate.swift; sourceTree = "<group>"; };
0450AC082EF11E4400B6AF06 /* StoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitManager.swift; sourceTree = "<group>"; }; 0450AC082EF11E4400B6AF06 /* StoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitManager.swift; sourceTree = "<group>"; };
0450AC1A2EF11E4400B6AF06 /* KBStoreKitBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KBStoreKitBridge.swift; sourceTree = "<group>"; };
0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinCenterVC.h; sourceTree = "<group>"; }; 0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinCenterVC.h; sourceTree = "<group>"; };
0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = "<group>"; }; 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = "<group>"; };
0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = "<group>"; }; 0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = "<group>"; };
@@ -802,6 +804,7 @@
0450AC052EF11E4400B6AF06 /* Models */, 0450AC052EF11E4400B6AF06 /* Models */,
0450AC072EF11E4400B6AF06 /* Protocols */, 0450AC072EF11E4400B6AF06 /* Protocols */,
0450AC082EF11E4400B6AF06 /* StoreKitManager.swift */, 0450AC082EF11E4400B6AF06 /* StoreKitManager.swift */,
0450AC1A2EF11E4400B6AF06 /* KBStoreKitBridge.swift */,
); );
path = StoreKit2Manager; path = StoreKit2Manager;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1917,6 +1920,7 @@
0450AC152EF11E4400B6AF06 /* TransactionHistory.swift in Sources */, 0450AC152EF11E4400B6AF06 /* TransactionHistory.swift in Sources */,
0450AC162EF11E4400B6AF06 /* StoreKitServiceDelegate.swift in Sources */, 0450AC162EF11E4400B6AF06 /* StoreKitServiceDelegate.swift in Sources */,
0450AC172EF11E4400B6AF06 /* StoreKitState.swift in Sources */, 0450AC172EF11E4400B6AF06 /* StoreKitState.swift in Sources */,
0450AC1B2EF11E4400B6AF06 /* KBStoreKitBridge.swift in Sources */,
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */, 043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */,
049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */, 049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */,
04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */, 04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */,

View File

@@ -44,7 +44,7 @@
/// 3 /// 3
/// KBLoginVM /// KBLoginVM
if ([KBUserSessionManager shared].isLoggedIn) { if ([KBUserSessionManager shared].isLoggedIn) {
[[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]]; // [[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]];
} }

View File

@@ -23,7 +23,7 @@
+ (void)kb_configureIAPIfNeeded { + (void)kb_configureIAPIfNeeded {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
[[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]]; // [[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]];
}); });
} }

View File

@@ -9,10 +9,17 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString * const KBIAPDidCompletePurchaseNotification; FOUNDATION_EXPORT NSNotificationName const KBIAPDidCompletePurchaseNotification;
@interface IAPVerifyTransactionObj : NSObject <FGIAPVerifyTransaction> @interface IAPVerifyTransactionObj : NSObject <FGIAPVerifyTransaction>
/// 校验票据StoreKit 2 入口)
/// - Parameters:
/// - receipt: Base64 编码的票据
/// - completion: 回调success 表示验签成功statusCode 为后端状态码
- (void)verifyReceipt:(NSString *)receipt
completion:(void (^)(BOOL success, NSString * _Nullable message, NSInteger statusCode))completion;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -9,7 +9,7 @@
//#import "KBLoginSheetViewController.h" //#import "KBLoginSheetViewController.h"
#import "KBBizCode.h" #import "KBBizCode.h"
NSString * const KBIAPDidCompletePurchaseNotification = @"KBIAPDidCompletePurchaseNotification"; NSNotificationName const KBIAPDidCompletePurchaseNotification = @"KBIAPDidCompletePurchaseNotification";
@interface IAPVerifyTransactionObj () @interface IAPVerifyTransactionObj ()
@property (nonatomic, strong) PayVM *payVM; @property (nonatomic, strong) PayVM *payVM;
@end @end
@@ -23,6 +23,29 @@ NSString * const KBIAPDidCompletePurchaseNotification = @"KBIAPDidCompletePurcha
return self; return self;
} }
- (void)verifyReceipt:(NSString *)receipt
completion:(void (^)(BOOL success, NSString * _Nullable message, NSInteger statusCode))completion {
if (![receipt isKindOfClass:NSString.class] || receipt.length == 0) {
if (completion) {
completion(NO, KBLocalized(@"Receipt missing"), KBBizCodeReceiptError);
}
return;
}
NSDictionary *params = @{ @"receipt": receipt ?: @"" };
__weak typeof(self) weakSelf = self;
[self.payVM applePayReqWithParams:params needShow:NO completion:^(NSInteger sta, NSString * _Nullable msg) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
(void)self;
BOOL success = (sta == KBBizCodeSuccess);
NSString *tip = msg ?: (success ? KBLocalized(@"Success") : KBLocalized(@"Payment failed"));
if (completion) {
completion(success, tip, sta);
}
});
}];
}
#pragma mark - FGIAPVerifyTransaction #pragma mark - FGIAPVerifyTransaction
- (void)pushSuccessTradeReultToServer:(NSString *)receipt - (void)pushSuccessTradeReultToServer:(NSString *)receipt
@@ -40,34 +63,23 @@ NSString * const KBIAPDidCompletePurchaseNotification = @"KBIAPDidCompletePurcha
#endif #endif
NSInteger type = 0;
#if DEBUG
type = 0;
#else
type = 1;
#endif
// , @"type": @(type)
NSDictionary *params = @{ @"receipt": receipt ?: @""};
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[KBHUD showWithStatus:@"请稍等..."]; [KBHUD showWithStatus:@"请稍等..."];
[self.payVM applePayReqWithParams:params needShow:NO completion:^(NSInteger sta, NSString * _Nullable msg) { [self verifyReceipt:receipt completion:^(BOOL success, NSString * _Nullable message, NSInteger statusCode) {
// [KBHUD dismiss]; __strong typeof(weakSelf) self = weakSelf;
// [KBHUD showInfo:(sta == !KBBizCodeSuccess ? KBLocalized(@"Payment failed") : KBLocalized(@"Payment successful"))]; (void)self;
if (sta == KBBizCodeSuccess) { if (success) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
[KBHUD showInfo:@"Success"]; [KBHUD showInfo:@"Success"];
[[NSNotificationCenter defaultCenter] postNotificationName:KBIAPDidCompletePurchaseNotification object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KBIAPDidCompletePurchaseNotification object:nil];
if (handler) handler(KBLocalized(@"Success"), nil); if (handler) handler(KBLocalized(@"Success"), nil);
}else if(sta == KBBizCodeReceiptError){ } else if (statusCode == KBBizCodeReceiptError) {
[KBHUD dismiss]; [KBHUD dismiss];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} else { } else {
[KBHUD showError:@"Failed"]; [KBHUD showError:message ?: KBLocalized(@"Failed")];
if (handler) handler(KBLocalized(@"Failed"), nil); if (handler) handler(message ?: KBLocalized(@"Failed"), nil);
} }
(void)weakSelf; // keep self during block life if needed
}]; }];
} }

View File

@@ -2,3 +2,4 @@
// Use this file to import your target's public headers that you would like to expose to Swift. // Use this file to import your target's public headers that you would like to expose to Swift.
// //
#import "IAPVerifyTransactionObj.h"

View File

@@ -0,0 +1,154 @@
//
// KBStoreKitBridge.swift
// keyBoard
//
// Created to expose StoreKit2Manager to Objective-C callers.
//
import Foundation
import StoreKit
@objcMembers
final class KBStoreKitBridge: NSObject, StoreKitDelegate {
static let shared = KBStoreKitBridge()
private let manager = StoreKit2Manager.shared
private let receiptVerifier = IAPVerifyTransactionObj()
private var configuredProductIds: Set<String> = []
private var configuredLifetimeIds: Set<String> = []
private var hasConfigured = false
private override init() {
super.init()
}
// MARK: - Preparation
func prepare(withProductIds productIds: [String], completion: ((Bool, String?) -> Void)? = nil) {
prepare(withProductIds: productIds, lifetimeIds: [], completion: completion)
}
func prepare(withProductIds productIds: [String], lifetimeIds: [String], completion: ((Bool, String?) -> Void)? = nil) {
Task {
let success = await self.configureIfNeeded(productIds: productIds, lifetimeIds: lifetimeIds)
await MainActor.run {
completion?(success, success ? nil : "Unable to load products.")
}
}
}
// MARK: - Purchase
func purchase(productId: String, completion: @escaping (Bool, String?) -> Void) {
Task {
let ready = await self.configureIfNeeded(productIds: [productId], lifetimeIds: [])
guard ready else {
await MainActor.run {
completion(false, "Unable to load product.")
}
return
}
do {
try await self.manager.purchase(productId: productId)
let state = self.manager.currentState
switch state {
case .purchaseSuccess(let id) where id == productId:
let receipt = try await Self.fetchReceiptData()
self.verifyReceipt(receipt, completion: completion)
case .purchasePending(let id) where id == productId:
await MainActor.run {
completion(false, "Purchase pending approval.")
}
case .purchaseCancelled(let id) where id == productId:
await MainActor.run {
completion(false, "Purchase cancelled.")
}
case .purchaseFailed(let id, let error) where id == productId:
await MainActor.run {
completion(false, error.localizedDescription)
}
case .error(let error):
await MainActor.run {
completion(false, error.localizedDescription)
}
default:
await MainActor.run {
completion(false, "Purchase failed.")
}
}
} catch {
await MainActor.run {
completion(false, error.localizedDescription)
}
}
}
}
// MARK: - Private Helpers
@MainActor
private func configureIfNeeded(productIds: [String], lifetimeIds: [String]) async -> Bool {
let ids = productIds.filter { !$0.isEmpty }
guard !ids.isEmpty else { return false }
let newProducts = Set(ids)
let newLifetime = Set(lifetimeIds.filter { !$0.isEmpty })
var needsConfigure = !hasConfigured
if !newProducts.isSubset(of: configuredProductIds) {
configuredProductIds.formUnion(newProducts)
needsConfigure = true
}
if !newLifetime.isSubset(of: configuredLifetimeIds) {
configuredLifetimeIds.formUnion(newLifetime)
needsConfigure = true
}
if needsConfigure {
let config = StoreKitConfig(
productIds: Array(configuredProductIds),
lifetimeIds: Array(configuredLifetimeIds)
)
manager.configure(with: config, delegate: self)
hasConfigured = true
}
await manager.refreshProducts()
return true
}
private func verifyReceipt(_ receipt: String, completion: @escaping (Bool, String?) -> Void) {
receiptVerifier.verifyReceipt(receipt) { success, message, _ in
DispatchQueue.main.async {
if success {
NotificationCenter.default.post(
name: NSNotification.Name(rawValue: NSNotification.Name.KBIAPDidCompletePurchase.rawValue),
object: nil
)
}
completion(success, message)
}
}
}
private static func fetchReceiptData() async throws -> String {
if let receipt = try loadReceiptFromBundle() {
return receipt
}
try await AppStore.sync()
if let receipt = try loadReceiptFromBundle() {
return receipt
}
throw StoreKitError.verificationFailed
}
private static func loadReceiptFromBundle() throws -> String? {
guard let url = Bundle.main.appStoreReceiptURL else { return nil }
guard FileManager.default.fileExists(atPath: url.path) else { return nil }
let data = try Data(contentsOf: url, options: .alwaysMapped)
guard !data.isEmpty else { return nil }
return data.base64EncodedString()
}
}

View File

@@ -4,13 +4,12 @@
#import "KBJfPay.h" #import "KBJfPay.h"
#import "KBJfPayCell.h" #import "KBJfPayCell.h"
#import "FGIAPProductsFilter.h"
#import "FGIAPManager.h"
#import "PayVM.h" #import "PayVM.h"
#import "KBPayProductModel.h" #import "KBPayProductModel.h"
#import "KBBizCode.h" #import "KBBizCode.h"
#import "KBShopVM.h" #import "KBShopVM.h"
#import "IAPVerifyTransactionObj.h" #import "IAPVerifyTransactionObj.h"
#import "keyBoard-Swift.h"
static NSString * const kKBJfPayCellId = @"kKBJfPayCellId"; static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
@interface KBJfPay () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> @interface KBJfPay () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@@ -40,7 +39,6 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
@property (nonatomic, strong) NSArray<KBPayProductModel *> *data; // In-App @property (nonatomic, strong) NSArray<KBPayProductModel *> *data; // In-App
@property (nonatomic, assign) NSInteger selectedIndex; // @property (nonatomic, assign) NSInteger selectedIndex; //
@property (nonatomic, strong) FGIAPProductsFilter *filter;
@property (nonatomic, strong) PayVM *payVM; @property (nonatomic, strong) PayVM *payVM;
@property (nonatomic, strong) KBShopVM *shopVM; @property (nonatomic, strong) KBShopVM *shopVM;
@@ -50,7 +48,6 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
self.filter = [[FGIAPProductsFilter alloc] init];
self.payVM = [PayVM new]; self.payVM = [PayVM new];
self.shopVM = [KBShopVM new]; self.shopVM = [KBShopVM new];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
@@ -272,11 +269,25 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
self.data = products ?: @[]; self.data = products ?: @[];
self.selectedIndex = self.data.count > 0 ? 0 : NSNotFound; self.selectedIndex = self.data.count > 0 ? 0 : NSNotFound;
[self.collectionView reloadData]; [self.collectionView reloadData];
[self prepareStoreKitWithProducts:self.data];
[self selectItemAtCurrentIndexAnimated:NO]; [self selectItemAtCurrentIndexAnimated:NO];
}); });
}]; }];
} }
- (void)prepareStoreKitWithProducts:(NSArray<KBPayProductModel *> *)products {
if (![products isKindOfClass:NSArray.class] || products.count == 0) { return; }
NSMutableArray<NSString *> *ids = [NSMutableArray array];
for (KBPayProductModel *item in products) {
if (![item isKindOfClass:KBPayProductModel.class]) { continue; }
if (item.productId.length) {
[ids addObject:item.productId];
}
}
if (ids.count == 0) { return; }
[[KBStoreKitBridge shared] prepareWithProductIds:ids completion:nil];
}
- (KBPayProductModel *)currentSelectedProductItem { - (KBPayProductModel *)currentSelectedProductItem {
if (self.selectedIndex == NSNotFound) { return nil; } if (self.selectedIndex == NSNotFound) { return nil; }
if (self.selectedIndex < 0 || self.selectedIndex >= self.data.count) { return nil; } if (self.selectedIndex < 0 || self.selectedIndex >= self.data.count) { return nil; }
@@ -315,28 +326,12 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
} }
[KBHUD show]; [KBHUD show];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[self.filter requestProductsWith:[NSSet setWithObject:productId] completion:^(NSArray<SKProduct *> * _Nonnull products) { [[KBStoreKitBridge shared] purchaseWithProductId:productId completion:^(BOOL success, NSString * _Nullable message) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf; __strong typeof(weakSelf) self = weakSelf;
if (!self) { return; } NSString *tip = message.length ? message : (success ? KBLocalized(@"Payment successful") : KBLocalized(@"Payment failed"));
SKProduct *match = nil;
for (SKProduct *product in products) {
if ([product.productIdentifier isEqualToString:productId]) {
match = product;
break;
}
}
if (!match) {
[KBHUD dismiss]; [KBHUD dismiss];
[KBHUD showInfo:KBLocalized(@"Unable to load product information")]; [KBHUD showInfo:tip];
return; (void)self;
}
[[FGIAPManager shared].iap buyProduct:match onCompletion:^(NSString * _Nonnull message, FGIAPManagerPurchaseRusult result) {
dispatch_async(dispatch_get_main_queue(), ^{
// [KBHUD dismiss];
});
}];
});
}]; }];
} }

View File

@@ -11,9 +11,8 @@
#import "KBVipReviewListCell.h" #import "KBVipReviewListCell.h"
#import "PayVM.h" #import "PayVM.h"
#import "KBPayProductModel.h" #import "KBPayProductModel.h"
#import "FGIAPProductsFilter.h"
#import "FGIAPManager.h"
#import "KBBizCode.h" #import "KBBizCode.h"
#import "keyBoard-Swift.h"
static NSString * const kKBVipHeaderId = @"kKBVipHeaderId"; static NSString * const kKBVipHeaderId = @"kKBVipHeaderId";
static NSString * const kKBVipSubscribeCellId = @"kKBVipSubscribeCellId"; static NSString * const kKBVipSubscribeCellId = @"kKBVipSubscribeCellId";
@@ -33,7 +32,6 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
@property (nonatomic, strong) UILabel *agreementLabel; // @property (nonatomic, strong) UILabel *agreementLabel; //
@property (nonatomic, strong) UIButton *agreementButton; // Embership Agreement @property (nonatomic, strong) UIButton *agreementButton; // Embership Agreement
@property (nonatomic, strong) PayVM *payVM; @property (nonatomic, strong) PayVM *payVM;
@property (nonatomic, strong) FGIAPProductsFilter *filter;
@end @end
@@ -54,7 +52,6 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
make.height.mas_equalTo(224); make.height.mas_equalTo(224);
}]; }];
self.payVM = [PayVM new]; self.payVM = [PayVM new];
self.filter = [[FGIAPProductsFilter alloc] init];
self.plans = @[]; self.plans = @[];
self.selectedIndex = NSNotFound; self.selectedIndex = NSNotFound;
@@ -120,11 +117,25 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
self.plans = products ?: @[]; self.plans = products ?: @[];
self.selectedIndex = self.plans.count > 0 ? 0 : NSNotFound; self.selectedIndex = self.plans.count > 0 ? 0 : NSNotFound;
[self.collectionView reloadData]; [self.collectionView reloadData];
[self prepareStoreKitWithPlans:self.plans];
[self selectCurrentPlanAnimated:NO]; [self selectCurrentPlanAnimated:NO];
}); });
}]; }];
} }
- (void)prepareStoreKitWithPlans:(NSArray<KBPayProductModel *> *)plans {
if (![plans isKindOfClass:NSArray.class] || plans.count == 0) { return; }
NSMutableArray<NSString *> *ids = [NSMutableArray array];
for (KBPayProductModel *plan in plans) {
if (![plan isKindOfClass:KBPayProductModel.class]) { continue; }
if (plan.productId.length) {
[ids addObject:plan.productId];
}
}
if (ids.count == 0) { return; }
[[KBStoreKitBridge shared] prepareWithProductIds:ids completion:nil];
}
- (void)selectCurrentPlanAnimated:(BOOL)animated { - (void)selectCurrentPlanAnimated:(BOOL)animated {
if (self.selectedIndex == NSNotFound) { return; } if (self.selectedIndex == NSNotFound) { return; }
if (self.selectedIndex < 0 || self.selectedIndex >= self.plans.count) { return; } if (self.selectedIndex < 0 || self.selectedIndex >= self.plans.count) { return; }
@@ -191,28 +202,14 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
} }
[KBHUD show]; [KBHUD show];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[self.filter requestProductsWith:[NSSet setWithObject:productId] completion:^(NSArray<SKProduct *> * _Nonnull products) { [[KBStoreKitBridge shared] purchaseWithProductId:productId completion:^(BOOL success, NSString * _Nullable message) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf; __strong typeof(weakSelf) self = weakSelf;
if (!self) { return; } NSString *tip = message.length ? message : (success ? KBLocalized(@"Payment successful") : KBLocalized(@"Payment failed"));
SKProduct *match = nil;
for (SKProduct *product in products) {
if ([product.productIdentifier isEqualToString:productId]) {
match = product;
break;
}
}
if (!match) {
[KBHUD dismiss]; [KBHUD dismiss];
[KBHUD showInfo:KBLocalized(@"Unable to load product information")]; [KBHUD showInfo:tip];
return; if (!self || !success) { return; }
} // UI
[[FGIAPManager shared].iap buyProduct:match onCompletion:^(NSString * _Nonnull message, FGIAPManagerPurchaseRusult result) { [self selectCurrentPlanAnimated:NO];
dispatch_async(dispatch_get_main_queue(), ^{
// [KBHUD dismiss];
});
}];
});
}]; }];
} }
- (void)agreementButtonAction{ - (void)agreementButtonAction{