转载注明出处:https://www.jianshu.com/p/8c958e75f98f


刚刚做了内购, 记录一下
这里直接上代码, 至于写代码之前的一些设置工作参考以下文章:
http://www.jianshu.com/p/690a7c68664e
http://www.jianshu.com/p/86ac7d3b593a

需要注意的是:

  1. 只要工程配置了对应的证书, 就能请求商品信息, 不需要任何其他处理
  2. 沙盒测试填写的邮箱不能是已经绑定appleID的邮箱, 也不能是AppleID的救援邮箱, 其他的无所谓, 其实, 哪怕你填写的邮箱不存在也没有关系
//
//  IAPManager.m
//  SpeakEnglish
//
//  Created by Daniel on 16/6/8.
//  Copyright © 2016年 Daniel. All rights reserved.
//#import "IAPManager.h"
#import <StoreKit/StoreKit.h>@interface IAPManager ()<SKPaymentTransactionObserver, SKProductsRequestDelegate>
// 所有商品
@property (nonatomic, strong)NSArray *products;
@property (nonatomic, strong)SKProductsRequest *request;
@endstatic IAPManager *manager = nil;@implementation IAPManager+ (instancetype)shareIAPManager {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{manager = [self new];[[SKPaymentQueue defaultQueue] addTransactionObserver:manager];});return manager;
}- (void)dealloc {[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}// 请求可卖的商品
- (void)requestProducts
{if (![SKPaymentQueue canMakePayments]) {// 您的手机没有打开程序内付费购买return;}// 1.请求所有的商品IDNSString *productFilePath = [[NSBundle mainBundle] pathForResource:@"iapdemo.plist" ofType:nil];NSArray *products = [NSArray arrayWithContentsOfFile:productFilePath];// 2.获取所有的productidNSArray *productIds = [products valueForKeyPath:@"productId"];// 3.获取productid的set(集合中)NSSet *set = [NSSet setWithArray:productIds];// 4.向苹果发送请求,请求可卖商品_request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];_request.delegate = self;[_request start];
}/***  当请求到可卖商品的结果会执行该方法**  @param response response中存储了可卖商品的结果*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{for (SKProduct *product in response.products) {// 用来保存价格NSMutableDictionary *priceDic = @{}.mutableCopy;// 货币单位NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];[numberFormatter setLocale:product.priceLocale];// 带有货币单位的价格NSString *formattedPrice = [numberFormatter stringFromNumber:product.price];[priceDic setObject:formattedPrice forKey:product.productIdentifier];NSLog(@"价格:%@", product.price);NSLog(@"标题:%@", product.localizedTitle);NSLog(@"秒速:%@", product.localizedDescription);NSLog(@"productid:%@", product.productIdentifier);}// 保存价格列表[[NSUserDefaults standardUserDefaults] setObject:priceDic forKey:@"priceDic"];[[NSUserDefaults standardUserDefaults] synchronize];// 1.存储所有的数据self.products = response.products;self.products = [self.products sortedArrayWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(SKProduct *obj1, SKProduct *obj2) {return [obj1.price compare:obj2.price];}];
}#pragma mark - 购买商品
- (void)buyProduct:(SKProduct *)product
{// 1.创建票据SKPayment *payment = [SKPayment paymentWithProduct:product];WELog(@"productIdentifier----%@", payment.productIdentifier);// 2.将票据加入到交易队列中[[SKPaymentQueue defaultQueue] addPayment:payment];
}#pragma mark - 实现观察者回调的方法
/***  当交易队列中的交易状态发生改变的时候会执行该方法**  @param transactions 数组中存放了所有的交易*/
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{/*SKPaymentTransactionStatePurchasing, 正在购买SKPaymentTransactionStatePurchased, 购买完成(销毁交易)SKPaymentTransactionStateFailed, 购买失败(销毁交易)SKPaymentTransactionStateRestored, 恢复购买(销毁交易)SKPaymentTransactionStateDeferred 最终状态未确定*/for (SKPaymentTransaction *transaction in transactions) {switch (transaction.transactionState) {case SKPaymentTransactionStatePurchasing:WELog(@"用户正在购买");break;case SKPaymentTransactionStatePurchased:WELog(@"productIdentifier----->%@", transaction.payment.productIdentifier);[self buySuccessWithPaymentQueue:queue Transaction:transaction];break;case SKPaymentTransactionStateFailed:NSLog(@"购买失败");[queue finishTransaction:transaction];break;case SKPaymentTransactionStateRestored:NSLog(@"恢复购买");//TODO:向服务器请求补货,服务器补货完成后,客户端再完成交易单子//[queue finishTransaction:transaction];break;case SKPaymentTransactionStateDeferred:NSLog(@"最终状态未确定");break;default:break;}}
}- (void)buySuccessWithPaymentQueue:(SKPaymentQueue *)queue Transaction:(SKPaymentTransaction *)transaction {AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];NSDictionary *params = @{@"user_id":@"user_id",// 获取商品@"goods":[self goodsWithProductIdentifier:transaction.payment.productIdentifier]};[manager POST:@"url" parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {if ([responseObject[@"code"] intValue] == 200) {// 防止丢单, 必须在服务器确定后从交易队列删除交易// 如果不从交易队列上删除交易, 下次调用addTransactionObserver:, 仍然会回调'updatedTransactions'方法, 以此处理丢单WELog(@"购买成功");[queue finishTransaction:transaction];}} failure:^(NSURLSessionDataTask *task, NSError *error) {}];
}// 商品列表 也可以使用从苹果请求的数据, 具体细节自己视情况处理
// goods1 是商品的ID
- (NSString *)goodsWithProductIdentifier:(NSString *)productIdentifier {NSDictionary *goodsDic = [[NSUserDefaults standardUserDefaults] objectForKey:@"priceDic"];return goodsDic[productIdentifier];
}// 恢复购买
- (void)restore
{[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {// 恢复失败WELog(@"恢复失败");
}// 取消请求商品信息
- (void)dealloc {[_request cancel];
}
@end

补充:

对于丢单的交易,在执行初始化[[SKpaymentQueue defaultQueue] addTransactionObserver: self] 的时候,如果有未完成的交易,会直接回调updatedTransactions,并且进入case SKPaymentTransationStateRestored,此时把这些未完成的交易告知服务器进行补货,补货完成再通知客户端,客户端再执行completeTransaction关闭单子

总结:
内购有三个可能出现的问题

  1. 支付成功后, 没来得及向服务器发送交易成功的数据就退出应用, 导致丢单. 这个问题貌似不需要本地化数据也已经没问题了, 除非再次回调updatedTransactions方法时已经拿不到票据了, 这样才有必要本地存储票据.
  2. 无法绑定交易和对应的用户. 因为applicationUsername的存在这已经不是问题了.
  3. 只用别人的手机进行购买, 没来得及向服务器发送交易成功的数据就退出应用, 导致丢单. 如果别人再也不打开这个应用甚至删掉了, 目前看来, 没有办法解决

参考资料:

  1. 苹果内购二次验证 PHP代码
    http://my.oschina.net/qianglong/blog/503861

  2. In-App Purchase Programming Guide
    https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html#//apple_ref/doc/uid/TP40008267

  3. iPhone In App Purchase购买完成时验证transactionReceipt
    http://www.cnblogs.com/eagley/archive/2011/06/15/2081577.html