iOS 内购IAP(订阅)和恢复购买的理解及实现[成功可用]

首先介绍实现苹果内购的流程

购买

1.向苹果服务器 请求商品列表
2.苹果服务器 在回调中返回商品列表
3.向苹果服务器 请求购买商品列表的第一个
4.苹果服务器 在回调中返回购买结果
5.根据回调的结果 进行处理

恢复购买

1.向苹果服务器 请求恢复购买
2.苹果服务器 在回调中返回恢复购买结果
5.根据回调的结果 进行处理

下面是代码实现

下面是.h文件的实现

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface PQIAPManager : NSObject

+ (PQIAPManager *)sharedManager;
/**请求购买商品*/
- (void)requestProductWithId:(NSString *)productId;
/**请求恢复购买*/
- (void)restore;

@end

NS_ASSUME_NONNULL_END

下面是.m文件的实现

#import "PQIAPManager.h"
#import <StoreKit/StoreKit.h>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <AdSupport/AdSupport.h>

/**共享密钥*/
#define KSecretKey @""
/**凭证校验地址*/
#define KReceiptCheckUrl @""
/**订阅状态检验地址*/
#define KStateCheckUrl @""

@interface PQIAPManager()<SKPaymentTransactionObserver,SKProductsRequestDelegate,SKRequestDelegate>

// 交易是否结束
@property (nonatomic, assign) BOOL isRequestFinished;
// 判断是购买还是恢复
@property (nonatomic, assign) BOOL isRestore;

@end

@implementation PQIAPManager

// 单例
+ (instancetype)sharedManager{
    static dispatch_once_t pred;
    static PQIAPManager *manager = nil;
    dispatch_once(&pred, ^{
        manager = [[self alloc] init];
    });
    return manager;
}

// 创建时就开始监听购买事件
- (instancetype)init{
    
    if (self = [super init]) {
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        self.isRequestFinished = YES;
    }
    return self;
}

// 销毁时就移除监听事件
- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

继续在.m文件中添加方法

/**请求商品列表*/
- (void)requestProductWithId:(NSString *)productId {
    self.isRestore = NO;
    if (self.isRequestFinished) {
        
        if ([SKPaymentQueue canMakePayments]) { //用户允许app内购
            if (productId.length) {
                
                self.isRequestFinished = NO; //正在请求
                NSLog(@"%@商品正在请求中...",productId);
                
                NSArray *product = [[NSArray alloc] initWithObjects:productId, nil];
                NSSet *set = [NSSet setWithArray:product];
                SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
                productsRequest.delegate = self;
                [productsRequest start];
            }
        } else { //没有权限
            self.isRequestFinished = YES; //完成请求
            NSLog(@"没有购买权限");
        }
    } else {
        NSLog(@"上次请求购买未完成,请稍后重试!");
    }
}
// 请求购买商品列表的第一个
#pragma mark SKProductsRequestDelegate 请求商品列表后的回调
- (void)productsRequest:(nonnull SKProductsRequest *)request didReceiveResponse:(nonnull SKProductsResponse *)response {
    NSArray *product = response.products;
    if (product.count == 0){
        return;
    } else {
        if (response.invalidProductIdentifiers.count) {
            NSLog(@"商品请求错误,error:%@", response.invalidProductIdentifiers);
        }
        NSLog(@"商品详情:%@",[product[0] description]);
        NSLog(@"商品价格:%@",[product[0] price]);
        NSLog(@"商品名字:%@",[product[0] productIdentifier]);
        
        // 请求购买
        SKPayment *payment = [SKPayment paymentWithProduct:product[0]];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
}
// 处理请求购买后的回调
#pragma mark - SKProductsRequestDelegate 请求购买后的回调
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
    
    if (!self.isRestore) {
        for (SKPaymentTransaction *tran in transactions) {

            switch (tran.transactionState) {
                
                case SKPaymentTransactionStatePurchasing: // 正在交易
                    break;
                
                case SKPaymentTransactionStatePurchased: // 交易完成
                    [self completeTransaction:tran];

                    if (tran.originalTransaction) {
                      // 如果是自动续费的订单,originalTransaction会有内容
                      if (tran.originalTransaction.transactionIdentifier) {
                        NSLog(@"自动续费的订单ID = %@",tran.originalTransaction.transactionIdentifier);
                        [self saveOrderId:tran.originalTransaction.transactionIdentifier];
                      }
                    } else {
                      // 普通购买,以及第一次购买自动订阅
                      if (tran.transactionIdentifier) {
                        NSLog(@"普通购买,以及第一次购买自动订阅ID = %@",tran.transactionIdentifier);
                        [self saveOrderId:tran.transactionIdentifier];
                       }
                     }

                    NSLog(@"购买完成,正在校验购买凭证...");
                    [self vertifyReceipt]; // 校验购买凭证
                    break;
                
                case SKPaymentTransactionStateFailed: // 交易失败
                    [self completeTransaction:tran];
                    NSLog(@"购买失败!");
                    break;
                
                case SKPaymentTransactionStateRestored: // 已经购买过该商品
                    [self completeTransaction:tran];
                    NSLog(@"已经购买过该商品!");
                    break;
                
                default:
                    break;
            }
        }
    }
}

- (void)completeTransaction:(SKPaymentTransaction *)transaction {   
    self.isRequestFinished = YES; // 请求完成
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
// 根据公司需求校验凭证
- (void)vertifyReceipt{
    
    NSURL * receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]){
        
        [self vertifyReceiptSuccess:^{
            NSLog(@"校验成功!");
        } failure:^{
            NSLog(@"校验失败!");
        }];
        
    } else {
        // 如果凭证为空,则再发一次凭证请求
        SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
        refreshReceiptRequest.delegate = self;
        [refreshReceiptRequest start];
    }
}
// 如果凭证为空,需要再发一次凭证请求,处理请求成功后的回调
#pragma mark -  成功请求凭证后的回调
- (void)requestDidFinish:(SKRequest *)request{
    if ([request isKindOfClass:[SKReceiptRefreshRequest class]]) {
        NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
        if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptUrl path]]) {
            [self vertifyReceiptSuccess:^{
              NSLog(@"再次请求凭证后,校验成功!");
            } failure:^{
              NSLog(@"再次请求凭证后,校验失败!");
            }];
        }
    }
}
// 校验凭证
- (void)vertifyReceiptSuccess:(void(^)(void))success failure:(void(^)(void))failure {
    NSString *receipt = [self getReceipt];
    // 校验凭证步骤需要按照你公司的需求来写
}

// 检验订单是否还在订阅期内
- (void)checkOrderStateSuccess:(void(^)(void))success failure:(void(^)(void))failure {
    NSString *receipt = [self getOrderId];
    // 检验订单步骤需要按照你公司的需求来写
}

// 获取凭证
- (NSString *)getReceipt {
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    NSString *encodeReceiptStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    return encodeReceiptStr;
}

// 保存订单ID
- (void)saveOrderId:(NSString *)orderId{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setValue:orderId forKey:@"orderId"];
    [defaults synchronize];
}

// 获取订单ID
- (NSString *)getOrderId{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return [defaults valueForKey:@"orderId"];
}

恢复购买

// 恢复购买
- (void)restore {
    self.isRestore = YES;
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
 // 恢复成功后的回调
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{

    if (queue.transactions.count) {
        for (SKPaymentTransaction *tran in queue.transactions) {
            if (tran.originalTransaction.transactionIdentifier) {
                [self saveOrderId:tran.originalTransaction.transactionIdentifier];
                break;
            }
        }
        // 检验订单是否还在订阅期内
        [self checkOrderStateSuccess:^{
            NSLog(@"订单还在订阅期,校验成功!");
        } failure:^{
            NSLog(@"订单已过期,校验失败!");
        }];
    } else {
        // 没有可恢复的购买项
    }
}
 // 恢复失败后的回调
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
     // 没有可恢复的购买项
}

最后

本篇主要讲述了一个详细的内购流程及代码实现,感兴趣的给个赞或者关注~~~
你们的支持就是我更新的动力!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容