1、什么样的应用需要使用内购?
需要使用内购:在APP中卖的东西是自己APP中的内容或者是只能在自己APP消费(常见的内购有:游戏中的道具、音频、视频)
不需要使用内购:常见的应用类型有:电商类、美团、饿了么
2、内购步骤
2-1:在iTunes Connect中的功能选项卡中有APP内购买项目中添加你要卖的产品(如下图:)
2-2:下面就是代码部分
2-2-1:需要导入的头文件
//内购头文件
#import <StoreKit/StoreKit.h>
2-2-2:内购代理方法
<SKProductsRequestDelegate, SKPaymentTransactionObserver>
#pragma mark - SKProductsRequestDelegate 代理方法
/**
* 获取可卖产品
*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response;
#pragma mark - SKPaymentQueue回调方法
/*
队列中的交易发生改变时,就会调用该方法
*/
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
2-3:获取内购产品方式
2-3-1:方式一:灵活配置
为了把内购产品在APP中展示出来,最好把我们在iTunes Connect中的 功能 选项卡中有 APP内购买项目 中添加你要卖的产品ID告诉 自己的服务器 。(优点:当想要修改产品的价格时,只需要服务器修改和iTunesConnect修改的一致就可以实现了,达到了动态修改的目的
2-3-2:方式二:配置写死
只需要在客服端代码中把产品ID写到一个数组即可
2-4:我们的项目获取内购产品是采用的方式一
第一步:从自己的服务器获取内购产品信息列表,主要是为了显示,便于选择购买(如下图所示:)
第二步:向自己的服务器发起请求来获取,订单信息。
第三步:最后把内购添加到交易队列中
第四步:然后付款支付
第五步:监听支付结果,进行相应处理
第六步:进行支付结果验证:
1、服务器验证:优点是安全
2、客户端验证:缺点是不安全
3、内购主要代码
//1、开始内购
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//传入商品下标,来访问自己的服务器发送请求
[self requestProductIndex:(int)indexPath.row];
}
#pragma mark - 传入商品下标,来访问自己的服务器发送请求
/**
* 传入商品下标,来访问自己的服务器发送请求
*/
- (void) requestProductIndex:(int) productIndex {
//1、获取商品模型(vipMsgs内购信息列表数组)
BeanAsset *asset = self.vipMsgs[productIndex];
// RWLog(@"%@", asset);
//2、想自己的服务器发送请求
[self requestProductMsgBeanAsset:asset];
}
#pragma mark - 请求 商品订单信息
- (void)requestProductMsgBeanAsset:(BeanAsset *)vipAsset {
NSString *priceStr = [NSString stringWithFormat:@"%@", [vipAsset getPrice]];
NSString *idStr = [NSString stringWithFormat:@"%@", [vipAsset getId]];
[BusinessInterfaceBz buyWithNSString:priceStr withNSString:idStr withHttpIResult:self];
}
//请求商品订单信息成功回调
//3、向【苹果服务器发送购买请求】
[weakSelf showOkayCancelAlertMessage:showString indexPath:weakSelf.productIndex];
//把交易添加到队列中
//1、获取可卖商品
[weakSelf buyProductsWithIndexPath:indexPath];
//2、
#pragma mark - 获取可卖商品
/**
* 获取可卖商品
*/
- (void)buyProductsWithIndexPath:(int) indexPath {
//1.1、当从 【苹果服务器】中取到了可卖商品
if (self.products.count) {
//1、取出模型(苹果代理方法中会把我们在iTunes中添加的内购商品返回来,在products数组中)
SKProduct *product = self.products[indexPath];
//2、购买商品
[self buyProduct:product];
}else {
//1.2、当没有从【苹果服务器】中获取到可卖的商品
// [self productsMessageRequest];
}
}
//3、最后把交易添加到交易队列中
#pragma mark - 购买商品
- (void)buyProduct:(SKProduct *) product {
//1、创建票据
SKPayment *payment = [SKPayment paymentWithProduct:product];
//2、将票据 添加到 交易队列中
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
4、监听苹果的代理方法来获取内购的商品
#pragma mark - SKProductsRequestDelegate 代理方法
/**
* 获取可卖产品
*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
self.products = response.products;
//1、展示商品
self.products = [response.products sortedArrayWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(SKProduct *obj1, SKProduct *obj2) {
//返回的数组 按钮【价格排序】
return [obj1.price compare:obj2.price];
}];
}
5、监听交易返回结果
#pragma mark - SKPaymentQueue回调方法
/*
队列中的交易发生改变时,就会调用该方法
*/
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
/*
SKPaymentTransactionStatePurchasing, 正在购买
SKPaymentTransactionStatePurchased, 已经购买(购买成功)
SKPaymentTransactionStateFailed, 购买失败
SKPaymentTransactionStateRestored, 恢复购买
SKPaymentTransactionStateDeferred 未决定
*/
for (SKPaymentTransaction *transation in transactions) {
switch (transation.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(@"用户正在购买");
break;
case SKPaymentTransactionStatePurchased:
NSLog(@"购买成功,将对应的商品给用户");
[self completeTransaction:transation];
break;
case SKPaymentTransactionStateFailed:
NSLog(@"购买失败,告诉用户没有付钱成功");
[self failedTransaction:transation];
break;
case SKPaymentTransactionStateRestored:
NSLog(@"恢复商品,将对应的商品给用户");
[self restoreTransaction:transation];
break;
case SKPaymentTransactionStateDeferred:
NSLog(@"未决定");
break;
default:
break;
}
}
}
#pragma mark - 完成交易
//交易结束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
//防止在block中循环引用
__unsafe_unretained __typeof(self) weakSelf = self;
// //交易验证
NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
// 接受到的App Store验证字符串,这里需要经过JSON编码
NSString* jsonObjectString = [self encode:(uint8_t *)receipt.bytes length:receipt.length];
self.jsonObjectString = jsonObjectString;
RWLog(@"order%@", self.order);
NSString *orderStr = nil;
if (self.order ) {
orderStr = [NSString stringWithFormat:@"%@", [self.order getTradeno]];
}
#pragma mark - 数据库存储订单信息
_RW_fmdb = [RWFMDB rw_fmdb];
//插入数据
[_RW_fmdb insertOrderDBOrderStr:orderStr addWithJsonData:jsonObjectString];
//打印出存储的内容
// [_RW_fmdb queryOrderDB];
// NSArray *arr = [_RW_fmdb queryOrderDB];
// NSLog(@"打印出存储的内容== %zd,%@",arr.count ,[_RW_fmdb queryOrderDB]);
//给服务器发送订单号
[BusinessInterfaceBz sendIosBuyMsgWithNSString:orderStr withNSString:@"TRADE_SUCCESS" withNSString:jsonObjectString withHttpIResult:self];
//【菊花】提示正在购买
[MBProgressHUD showMessage:@"订单传输中..."];
// 接受到的App Store验证字符串,这里需要经过JSON编码
// NSString* jsonObjectString = [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];
// RWLog(@"%@", jsonObjectString);
// 将jsonObjectString字符串发给服务器,由服务器POST到iTunes上验证
NSString* sendString = [[NSString alloc] initWithFormat:@"{\"receipt-data\":\"%@\"}",jsonObjectString ];
// @"https://sandbox.itunes.apple.com/verifyReceipt"
// @"https://buy.itunes.apple.com/verifyReceipt"
NSURL *sandboxStoreURL = [[NSURL alloc] initWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];
NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:sandboxStoreURL];
[connectionRequest setHTTPMethod:@"POST"];
[connectionRequest setTimeoutInterval:120.0];
[connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy];
[connectionRequest setHTTPBody:postData];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:connectionRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (!jsonResponse) { /* ... Handle error ...*/ }
/* ... Send a response back to the device ... */
//Parse the Response
RWLog(@"json==json:%@", jsonResponse);
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonResponse options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
RWLog(@"%@", jsonString);
[weakSelf updateVIPUserMsg];
}
}];
// [self completeTransaction:transaction jsonWithNString:jsonObjectString iosSandBoxType:NO orderNum:[self.order getPrice].integerValue];
}
// base64编码
- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length
{
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t *output = (uint8_t *)data.mutableBytes;
for (NSInteger i = 0; i < length; i += 3) {
NSInteger value = 0;
for (NSInteger j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger index = (i / 3) * 4;
output[index + 0] = table[(value >> 18) & 0x3F];
output[index + 1] = table[(value >> 12) & 0x3F];
output[index + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[index + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
// return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}
#pragma mark - 购买失败
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"购买失败");
} else {
NSLog(@"用户取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
6、小结(小心被拒)
在内购时,苹果审核时是不允许强制用户登录的,也就是用户时游客时也是可以进行内购的:
#pragma mark - 通过buttonIndex来判断点击了什么按钮(警告框从底部弹出)
-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
switch (buttonIndex) {
case 0:
//跳转到【注册界面】
[self skipRegisterInterface];
break;
case 1:
//游客模式购买
[self requestProductIndex:self.productIndex];
break;
case 2:
NSLog(@"取消购买");
break;
default:
break;
}
}