當(dāng)前版本功能支持的功能
- 內(nèi)購(gòu)商品列表請(qǐng)求-基于Block回調(diào)風(fēng)格返回商品請(qǐng)求結(jié)果
- 內(nèi)購(gòu)商品預(yù)加載- 應(yīng)用啟動(dòng)后自動(dòng)向蘋(píng)果內(nèi)購(gòu)服務(wù)器請(qǐng)求內(nèi)購(gòu)商品列表
- 支持自動(dòng)訂閱
- 支持非消耗型內(nèi)購(gòu)商品購(gòu)買 如移除廣告脂信,功能解鎖等
- 支持本地內(nèi)購(gòu)訂單持久化
- 支持本地會(huì)員有效期查詢
- 支持已購(gòu)商品恢復(fù)購(gòu)買
- 支持不同權(quán)益檔次會(huì)員蛉幸,不同檔次權(quán)益有效期互不干擾
(如需實(shí)現(xiàn)高級(jí)會(huì)員過(guò)期后才生效普通會(huì)員,可根據(jù)各檔次會(huì)員有效期自行重新疊加計(jì)算有效期)
當(dāng)前版本暫不支持的功能
- 新用戶折扣優(yōu)惠購(gòu)買
- 老用戶折扣優(yōu)惠購(gòu)買
集成步驟及模塊初始化
1.將IAHelper工程拖入到想要集成內(nèi)購(gòu)模塊的 Workspace 中
2.將 IAPHelper.framework 添加到項(xiàng)目工程中
- 在需要用到 IAPHeper 模塊的地方跌造,導(dǎo)入 IAPHelper 模塊
#import <IAPHelper/IAPHelper.h>
4.在工程的AppDelegate的初始化方法中 設(shè)置程序支持的內(nèi)購(gòu)項(xiàng)目鼻由。
注意:無(wú)論內(nèi)購(gòu)項(xiàng)是否在售賣狀態(tài)省骂,只要曾經(jīng)有用戶購(gòu)買過(guò)惊橱,均需添加止吐,因?yàn)槠鋵?huì)決定用戶的相關(guān)權(quán)益有效期計(jì)算
+(void)initialize{
//NOTE: 商品信息錄入必須在內(nèi)購(gòu)模塊啟動(dòng)前
//自動(dòng)訂閱組
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];
//配置會(huì)員權(quán)益等計(jì)時(shí)型商品內(nèi)購(gòu)項(xiàng)宝踪。錄入黃金月度會(huì)員,黃金年度會(huì)員碍扔,周會(huì)員瘩燥,永久會(huì)員等
[[IAPaymentCommon shareInstance] configRenewalProductInfosFromDeveloper:@[
autoWeekProductInfo,autoMonthProductInfo,autoYearProductInfo,
monthProductInfoA,yearProductInfoA,foreverProductInfoA,
monthProductInfoB,yearProductInfoB,foreverProductInfoB
]];
//配置普通非消耗型內(nèi)購(gòu)項(xiàng)。例如:移除廣告內(nèi)購(gòu)不同,解鎖關(guān)卡內(nèi)購(gòu)
// IABaseProductInfo * removeAdsProduct = [[IABaseProductInfo alloc] initWithProductId:@"removeAds"];
// IABaseProductInfo * unlockMoreFuncProduct = [[IABaseProductInfo alloc] initWithProductId:@"unlockMoreFuncs"];
// [[IAPaymentCommon shareInstance] configNormalProductInfosFromDeveloper:@[removeAdsProduct,unlockMoreFuncProduct]];
}
- 在應(yīng)用啟動(dòng)時(shí)厉膀,調(diào)用 IAPaymentCommon 的 load 方法來(lái)完成內(nèi)購(gòu)模塊加載
注意:需要傳入的參數(shù)為該Apple開(kāi)發(fā)者賬號(hào)下的共享密鑰,該密鑰將影響本地購(gòu)買憑據(jù)驗(yàn)證二拐,進(jìn)而影響相關(guān)權(quán)益有效期計(jì)算
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//內(nèi)購(gòu)?fù)ㄓ媚K加載
[[IAPaymentCommon shareInstance] load:IAPSecretKey];
// 未正常完成的交易
// NSArray * unfinishedTransactions = [[IAPaymentCommon shareInstance] getUnFinishedPaymentTransactions];
// if (unfinishedTransactions.count) {
//
// }
return YES;
}
商品請(qǐng)求服鹅、購(gòu)買、恢復(fù)購(gòu)買
商品請(qǐng)求
商品請(qǐng)求是后臺(tái)靜默執(zhí)行的百新,不包含UI顯示(系統(tǒng)彈出的除外),故為了交互友好企软,請(qǐng)?jiān)诰唧w業(yè)務(wù)中添加相關(guān)等待提示框,狀態(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];
});
}];
另一種商品請(qǐng)求方式吟孙,可精確請(qǐng)求指定商品列表
[[IAPaymentCommon shareInstance] requestMultipleProductIds:[IAPaymentCommon shareInstance].allProductIds complateHandler:^(NSArray<IABaseProductInfo *> * _Nullable productArray, IAProductRequestStatus status, NSError * _Nullable error) {
//code here to refresh UI
}];
商品購(gòu)買
商品購(gòu)買是后臺(tái)靜默執(zhí)行的澜倦,不包含UI顯示(系統(tǒng)彈出的除外),故為了交互友好,請(qǐng)?jiān)诰唧w業(yè)務(wù)中添加相關(guān)等待提示框杰妓,狀態(tài)提示框UI顯示
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = NSLocalizedString(@"購(gòu)買中...", @"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 = @"正在生成支付訂單藻治,請(qǐng)稍后...";
}
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 ? @"購(gòu)買成功巷挥!" : @"購(gòu)買失斪选!";
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 = @"購(gòu)買已取消";
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];
}];
恢復(fù)購(gòu)買
商品恢復(fù)購(gòu)買是后臺(tái)靜默執(zhí)行的倍宾,不包含UI顯示(系統(tǒng)彈出的除外),故為了交互友好雏节,請(qǐng)?jiān)诰唧w業(yè)務(wù)中添加相關(guān)等待提示框,狀態(tài)提示框UI顯示
恢復(fù)購(gòu)買提供有兩個(gè) api 來(lái)實(shí)現(xiàn)高职,分別是
調(diào)用蘋(píng)果提供的恢復(fù)內(nèi)購(gòu)Api實(shí)現(xiàn)
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = NSLocalizedString(@"恢復(fù)購(gòu)買中钩乍,請(qǐng)稍后...", @"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];
}];
調(diào)用蘋(píng)果提供的刷新購(gòu)買憑據(jù)Api實(shí)現(xiàn)
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.label.text = NSLocalizedString(@"恢復(fù)購(gòu)買中,請(qǐng)稍后...", @"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];
}];
//恢復(fù)購(gòu)買完成
- (void)onRestoreComplete:(NSArray *)productIds restoreState:(BOOL)isSuccess error:(NSError *)error{
dispatch_async(dispatch_get_main_queue(), ^{
NSString * message = isSuccess ? @"恢復(fù)購(gòu)買成功怔锌!" : @"恢復(fù)購(gòu)買失斄却狻变过!";
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)兩種方式有質(zhì)的區(qū)別
刷新會(huì)員有效期
刷新普通會(huì)員有效期
//更新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];//獲取當(dāng)前時(shí)間
NSTimeInterval relativeTimeinterval = [relativetime timeIntervalSince1970];
self.vipExpireDateLabel.textColor = [UIColor systemBlueColor];
//當(dāng)前是否有已購(gòu)買且在有效期內(nèi)的自動(dòng)訂閱內(nèi)購(gòu)項(xiàng)
if (vipProductGroupInfo.hasActiveAutoRenewOrder) {
self.vipExpireDateLabel.text = @"VIP到期時(shí)間:自動(dòng)續(xù)訂中";
}else{
if (relativeTimeinterval > timeInterval) {
self.vipExpireDateLabel.textColor = [UIColor systemRedColor];
self.vipExpireDateLabel.text = [NSString stringWithFormat:@"VIP會(huì)員已于%@過(guò)期",expireDateString];
if (timeInterval == 0) {
self.vipExpireDateLabel.text = @"未開(kāi)通";
}
}
else{
if (timeInterval >= LONG_MAX) {
self.vipExpireDateLabel.textColor = [UIColor systemPinkColor];
self.vipExpireDateLabel.text = @"VIP到期時(shí)間:永不過(guò)期";
}else{
self.vipExpireDateLabel.text = [NSString stringWithFormat:@"VIP會(huì)員到期時(shí)間:%@",expireDateString];
}
}
}
}
刷新超級(jí)會(huì)員有效期(沒(méi)有可不處理)
//更新SVIP有效期
- (void)updatesSVIPExpireInfo{
//超級(jí)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];//獲取當(dāng)前時(shí)間
NSTimeInterval relativeTimeinterval = [relativetime timeIntervalSince1970];
self.svipExpireDateLabel.textColor = [UIColor systemBlueColor];
//當(dāng)前是否有已購(gòu)買且在有效期內(nèi)的自動(dòng)訂閱內(nèi)購(gòu)項(xiàng)
if (svipProductGroupInfo.hasActiveAutoRenewOrder) {
self.svipExpireDateLabel.text = @"SVIP到期時(shí)間:自動(dòng)續(xù)訂中";
}else{
if (relativeTimeinterval > timeInterval) {
self.svipExpireDateLabel.textColor = [UIColor systemRedColor];
self.svipExpireDateLabel.text = [NSString stringWithFormat:@"SVIP會(huì)員已于%@過(guò)期",expireDateString];
if (timeInterval == 0) {
self.svipExpireDateLabel.text = @"未開(kāi)通";
}
}
else{
if (timeInterval >= LONG_MAX) {
self.svipExpireDateLabel.textColor = [UIColor systemPinkColor];
self.svipExpireDateLabel.text = @"SVIP到期時(shí)間:永不過(guò)期";
}else{
self.svipExpireDateLabel.text = [NSString stringWithFormat:@"SVIP會(huì)員到期時(shí)間:%@",expireDateString];
}
}
}
}
憑據(jù)驗(yàn)證成功通知
因?yàn)楸緝?nèi)購(gòu)模塊中權(quán)益有效期計(jì)算是基于本地購(gòu)買憑據(jù)驗(yàn)證得來(lái)的,所以最真實(shí)的有效期信息應(yīng)該來(lái)源于憑據(jù)驗(yàn)證涝涤,故建議在需要展示vip或記錄vip有效期信息的地方注冊(cè)本地憑據(jù)驗(yàn)證成功的通知媚狰, 并于業(yè)務(wù)中刷新權(quán)益有效期信息
//已購(gòu)訂單憑據(jù)驗(yàn)證完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceiptValidCompleteNotification:) name:kReceiptValidCompleteNotification object:nil];
/* 內(nèi)購(gòu)商品憑據(jù)本地校驗(yàn)完成通知 */
- (void)onReceiptValidCompleteNotification:(NSNotification *)notification{
[self updateExpireDateInfo];;
}
商品請(qǐng)求成功通知
//商品請(qǐng)求完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onProductsRequestCompleteNotification:) name:kProductInfosRequestSucceedNotification object:nil];
來(lái)自AppStore商品購(gòu)買通知
//來(lái)自AppStore商品購(gòu)買通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppStorePaymentNotification:) name:kPaymentFromAppStoreShouldHandleNotification object:nil];