當前版本功能支持的功能
- 內(nèi)購商品列表請求-基于Block回調風格返回商品請求結果
- 內(nèi)購商品預加載- 應用啟動后自動向蘋果內(nèi)購服務器請求內(nèi)購商品列表
- 支持自動訂閱
- 支持非消耗型內(nèi)購商品購買 如移除廣告,功能解鎖等
- 支持本地內(nèi)購訂單持久化
- 支持本地會員有效期查詢
- 支持已購商品恢復購買
- 支持不同權益檔次會員,不同檔次權益有效期互不干擾
(如需實現(xiàn)高級會員過期后才生效普通會員,可根據(jù)各檔次會員有效期自行重新疊加計算有效期)
當前版本暫不支持的功能
- 新用戶折扣優(yōu)惠購買
- 老用戶折扣優(yōu)惠購買
集成步驟及模塊初始化
1.將IAHelper工程拖入到想要集成內(nèi)購模塊的 Workspace 中

image.png
2.將 IAPHelper.framework 添加到項目工程中

image.png
- 在需要用到 IAPHeper 模塊的地方,導入 IAPHelper 模塊
#import <IAPHelper/IAPHelper.h>
4.在工程的AppDelegate的初始化方法中 設置程序支持的內(nèi)購項目。
注意:無論內(nèi)購項是否在售賣狀態(tài),只要曾經(jīng)有用戶購買過,均需添加,因為其將會決定用戶的相關權益有效期計算
+(void)initialize{
//NOTE: 商品信息錄入必須在內(nèi)購模塊啟動前
//自動訂閱組
IARenewalProductInfo * autoWeekProductInfo = [[IARenewalProductInfo alloc] initWithProductId:Group_First_Auto_Renew_First_Level periodType:ProductPeriodPerWeek probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kVIPGroupId isAutoRenewType:YES];
IARenewalProductInfo * autoMonthProductInfo = [[IARenewalProductInfo alloc] initWithProductId:Group_First_Auto_Renew_Second_Level periodType:ProductPeriodPerMonth probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kVIPGroupId isAutoRenewType:YES];
IARenewalProductInfo * autoYearProductInfo = [[IARenewalProductInfo alloc] initWithProductId:Group_First_Auto_Renew_Third_Level periodType:ProductPeriodPerYear probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kVIPGroupId isAutoRenewType:YES];
//訂閱組A
IARenewalProductInfo * monthProductInfoA = [[IARenewalProductInfo alloc] initWithProductId:Program_A_First_Level_Original periodType:ProductPeriodPerMonth probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kVIPGroupId isAutoRenewType:NO];
IARenewalProductInfo * yearProductInfoA = [[IARenewalProductInfo alloc] initWithProductId:Program_A_Second_Level_Original periodType:ProductPeriodPerYear probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kVIPGroupId isAutoRenewType:NO];
IARenewalProductInfo * foreverProductInfoA = [[IARenewalProductInfo alloc] initWithProductId:Program_A_Third_Level_Original periodType:ProductPeriodForever probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kVIPGroupId isAutoRenewType:NO];
//訂閱組B
IARenewalProductInfo * monthProductInfoB = [[IARenewalProductInfo alloc] initWithProductId:Program_B_First_Level_Original periodType:ProductPeriodPerMonth probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kSVIPGroupId isAutoRenewType:NO];
IARenewalProductInfo * yearProductInfoB = [[IARenewalProductInfo alloc] initWithProductId:Program_B_Second_Level_Original periodType:ProductPeriodPerYear probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kSVIPGroupId isAutoRenewType:NO];
IARenewalProductInfo * foreverProductInfoB= [[IARenewalProductInfo alloc] initWithProductId:Program_B_Third_Level_Original periodType:ProductPeriodForever probabtionPeriodType:ProductProbationPeriodTypeNone groupId:kSVIPGroupId isAutoRenewType:NO];
//配置會員權益等計時型商品內(nèi)購項。錄入黃金月度會員,黃金年度會員,周會員,永久會員等
[[IAPaymentCommon shareInstance] configRenewalProductInfosFromDeveloper:@[
autoWeekProductInfo,autoMonthProductInfo,autoYearProductInfo,
monthProductInfoA,yearProductInfoA,foreverProductInfoA,
monthProductInfoB,yearProductInfoB,foreverProductInfoB
]];
//配置普通非消耗型內(nèi)購項。例如:移除廣告內(nèi)購,解鎖關卡內(nèi)購
// IABaseProductInfo * removeAdsProduct = [[IABaseProductInfo alloc] initWithProductId:@"removeAds"];
// IABaseProductInfo * unlockMoreFuncProduct = [[IABaseProductInfo alloc] initWithProductId:@"unlockMoreFuncs"];
// [[IAPaymentCommon shareInstance] configNormalProductInfosFromDeveloper:@[removeAdsProduct,unlockMoreFuncProduct]];
}
- 在應用啟動時,調用 IAPaymentCommon 的 load 方法來完成內(nèi)購模塊加載
注意:需要傳入的參數(shù)為該Apple開發(fā)者賬號下的共享密鑰,該密鑰將影響本地購買憑據(jù)驗證,進而影響相關權益有效期計算
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//內(nèi)購通用模塊加載
[[IAPaymentCommon shareInstance] load:IAPSecretKey];
// 未正常完成的交易
// NSArray * unfinishedTransactions = [[IAPaymentCommon shareInstance] getUnFinishedPaymentTransactions];
// if (unfinishedTransactions.count) {
//
// }
return YES;
}
商品請求、購買、恢復購買
商品請求
商品請求是后臺靜默執(zhí)行的,不包含UI顯示(系統(tǒng)彈出的除外),故為了交互友好,請在具體業(yè)務中添加相關等待提示框,狀態(tài)提示框UI顯示
[[IAPaymentCommon shareInstance] requestAllPreloadProductsWithCompletion:^(NSArray<IABaseProductInfo *> * _Nullable productArray, IAProductRequestStatus status, NSError * _Nullable error) {
if (!error && productArray) {
self.allProductInfos = [NSMutableArray arrayWithArray:productArray];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.refreshControl endRefreshing];
[self.indicatorView stopAnimating];
self.indicatorView.hidden = YES;
[self.tableView reloadData];
});
}];
另一種商品請求方式,可精確請求指定商品列表
[[IAPaymentCommon shareInstance] requestMultipleProductIds:[IAPaymentCommon shareInstance].allProductIds complateHandler:^(NSArray<IABaseProductInfo *> * _Nullable productArray, IAProductRequestStatus status, NSError * _Nullable error) {
//code here to refresh UI
}];
商品購買
商品購買是后臺靜默執(zhí)行的,不包含UI顯示(系統(tǒng)彈出的除外),故為了交互友好,請在具體業(yè)務中添加相關等待提示框,狀態(tài)提示框UI顯示
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = NSLocalizedString(@"購買中...", @"HUD loading title");
IABaseProductInfo * productInfo = self.allProductInfos[indexPath.row];
[[IAPaymentCommon shareInstance] purchaseProductByProductId:productInfo.productId purchasing:^(NSString * _Nonnull productId, IAPurchasingState purchasingState, SKPaymentTransaction * _Nonnull transaction) {
dispatch_async(dispatch_get_main_queue(), ^{
if (purchasingState == IAPurchasingStateOrderGenerating) {
hud.label.text = @"正在生成支付訂單,請稍后...";
}
else{
hud.label.text = @"訂單已生成,正在獲取訂單信息...";
}
});
} purchaseComplete:^(NSString * _Nonnull productId, SKPaymentTransaction * _Nullable paymentTransaction, BOOL isSuccess, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
NSString * message = isSuccess ? @"購買成功!" : @"購買失??!";
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
// Set the custom view mode to show any view.
hud.mode = MBProgressHUDModeCustomView;
// Set an image view with a checkmark.
UIImage *image = [[UIImage imageNamed:@"Checkmark"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
hud.customView = [[UIImageView alloc] initWithImage:image];
// Looks a bit nicer if we make it square.
hud.square = YES;
// Optional label text.
hud.label.text = message;
[hud hideAnimated:YES afterDelay:2.f];
if (isSuccess) {
[self updateExpireDateInfo];
}
});
} purchaseCancelled:^(NSString * _Nonnull productId, NSError * _Nullable error) {
[hud hideAnimated:YES];
NSString * message = @"購買已取消";
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
// Set the custom view mode to show any view.
hud.mode = MBProgressHUDModeCustomView;
// Set an image view with a checkmark.
UIImage *image = [[UIImage imageNamed:@"Checkmark"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
hud.customView = [[UIImageView alloc] initWithImage:image];
// Looks a bit nicer if we make it square.
hud.square = YES;
// Optional label text.
hud.label.text = message;
[hud hideAnimated:YES afterDelay:2.f];
}];
恢復購買
商品恢復購買是后臺靜默執(zhí)行的,不包含UI顯示(系統(tǒng)彈出的除外),故為了交互友好,請在具體業(yè)務中添加相關等待提示框,狀態(tài)提示框UI顯示
恢復購買提供有兩個 api 來實現(xiàn),分別是
調用蘋果提供的恢復內(nèi)購Api實現(xiàn)
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = NSLocalizedString(@"恢復購買中,請稍后...", @"HUD loading title");
[[IAPaymentCommon shareInstance] restorePurchaseWithComplete:^(NSArray * _Nonnull productIds, BOOL isSuccess, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
[self onRestoreComplete:productIds restoreState:isSuccess error:error];
}];
調用蘋果提供的刷新購買憑據(jù)Api實現(xiàn)
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = NSLocalizedString(@"恢復購買中,請稍后...", @"HUD loading title");
[[IAPaymentCommon shareInstance] refreshReceiptWithComplete:^(NSArray * _Nullable productIds, BOOL isSuccess, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
[self onRestoreComplete:productIds restoreState:isSuccess error:error];
}];
//恢復購買完成
- (void)onRestoreComplete:(NSArray *)productIds restoreState:(BOOL)isSuccess error:(NSError *)error{
dispatch_async(dispatch_get_main_queue(), ^{
NSString * message = isSuccess ? @"恢復購買成功!" : @"恢復購買失敗!";
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
// Set the custom view mode to show any view.
hud.mode = MBProgressHUDModeCustomView;
// Set an image view with a checkmark.
UIImage *image = [[UIImage imageNamed:@"Checkmark"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
hud.customView = [[UIImageView alloc] initWithImage:image];
// Looks a bit nicer if we make it square.
hud.square = YES;
// Optional label text.
hud.label.text = message;
[hud hideAnimated:YES afterDelay:2.f];
if (isSuccess) {
[self updateExpireDateInfo];
}
});
}
目前并未發(fā)現(xiàn)兩種方式有質的區(qū)別
刷新會員有效期
刷新普通會員有效期
//更新VIP有效期
- (void)updateVIPExpireInfo{
//普通vip
ProductGroupInfo * vipProductGroupInfo = [[IAPaymentCommon shareInstance] getProductGroupWithGroupId:kVIPGroupId];
NSTimeInterval timeInterval = vipProductGroupInfo.expireDateMs;
NSDate * expireDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
NSString * expireDateString = [formatter stringFromDate:expireDate];
NSDate * relativetime = [[IATimeManager defaultManager] getRelativetime];//獲取當前時間
NSTimeInterval relativeTimeinterval = [relativetime timeIntervalSince1970];
self.vipExpireDateLabel.textColor = [UIColor systemBlueColor];
//當前是否有已購買且在有效期內(nèi)的自動訂閱內(nèi)購項
if (vipProductGroupInfo.hasActiveAutoRenewOrder) {
self.vipExpireDateLabel.text = @"VIP到期時間:自動續(xù)訂中";
}else{
if (relativeTimeinterval > timeInterval) {
self.vipExpireDateLabel.textColor = [UIColor systemRedColor];
self.vipExpireDateLabel.text = [NSString stringWithFormat:@"VIP會員已于%@過期",expireDateString];
if (timeInterval == 0) {
self.vipExpireDateLabel.text = @"未開通";
}
}
else{
if (timeInterval >= LONG_MAX) {
self.vipExpireDateLabel.textColor = [UIColor systemPinkColor];
self.vipExpireDateLabel.text = @"VIP到期時間:永不過期";
}else{
self.vipExpireDateLabel.text = [NSString stringWithFormat:@"VIP會員到期時間:%@",expireDateString];
}
}
}
}
刷新超級會員有效期(沒有可不處理)
//更新SVIP有效期
- (void)updatesSVIPExpireInfo{
//超級vip
ProductGroupInfo * svipProductGroupInfo = [[IAPaymentCommon shareInstance] getProductGroupWithGroupId:kSVIPGroupId];
NSTimeInterval timeInterval = svipProductGroupInfo.expireDateMs;
NSDate * expireDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
NSString * expireDateString = [formatter stringFromDate:expireDate];
NSDate * relativetime = [[IATimeManager defaultManager] getRelativetime];//獲取當前時間
NSTimeInterval relativeTimeinterval = [relativetime timeIntervalSince1970];
self.svipExpireDateLabel.textColor = [UIColor systemBlueColor];
//當前是否有已購買且在有效期內(nèi)的自動訂閱內(nèi)購項
if (svipProductGroupInfo.hasActiveAutoRenewOrder) {
self.svipExpireDateLabel.text = @"SVIP到期時間:自動續(xù)訂中";
}else{
if (relativeTimeinterval > timeInterval) {
self.svipExpireDateLabel.textColor = [UIColor systemRedColor];
self.svipExpireDateLabel.text = [NSString stringWithFormat:@"SVIP會員已于%@過期",expireDateString];
if (timeInterval == 0) {
self.svipExpireDateLabel.text = @"未開通";
}
}
else{
if (timeInterval >= LONG_MAX) {
self.svipExpireDateLabel.textColor = [UIColor systemPinkColor];
self.svipExpireDateLabel.text = @"SVIP到期時間:永不過期";
}else{
self.svipExpireDateLabel.text = [NSString stringWithFormat:@"SVIP會員到期時間:%@",expireDateString];
}
}
}
}
憑據(jù)驗證成功通知
因為本內(nèi)購模塊中權益有效期計算是基于本地購買憑據(jù)驗證得來的,所以最真實的有效期信息應該來源于憑據(jù)驗證,故建議在需要展示vip或記錄vip有效期信息的地方注冊本地憑據(jù)驗證成功的通知, 并于業(yè)務中刷新權益有效期信息
//已購訂單憑據(jù)驗證完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceiptValidCompleteNotification:) name:kReceiptValidCompleteNotification object:nil];
/* 內(nèi)購商品憑據(jù)本地校驗完成通知 */
- (void)onReceiptValidCompleteNotification:(NSNotification *)notification{
[self updateExpireDateInfo];;
}
商品請求成功通知
//商品請求完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onProductsRequestCompleteNotification:) name:kProductInfosRequestSucceedNotification object:nil];
來自AppStore商品購買通知
//來自AppStore商品購買通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppStorePaymentNotification:) name:kPaymentFromAppStoreShouldHandleNotification object:nil];