内购坑多,爬了好久(起码简书里的内购相关文章几乎看过来完了),算是懂了点.对你有用告诉我,有疑问联系我.
开始(建议先去看写入门的)
@import StoreKit;
#import "IAPManager.h"
@implementation IAPManager
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[IAPManager alloc] init];
});
return sharedInstance;
}
单利的入口,传进来一个后台得到的商品ID(在苹果网站注册的)
- (void)payBtnPressed:(NSString *)product_id {//这传进来个商品id,lzc
if ([SKPaymentQueue canMakePayments])//是否允许应用内付费
{
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:product_id]];
request.delegate = self;
[request start];
}else
DLog(@"用户不允许内购");//提示框
}
// 查询成功后的回调
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
//这里菊花消失
DLog(@"菊花消失");
NSArray *products = response.products;
if (products.count != 0) {
self.product = products[0];
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:self.product];//lzc 改
payment.applicationUsername = @(1000000).stringValue;//充值用户的id,也就是uid.
[[SKPaymentQueue defaultQueue] addPayment:payment];//发起购买
当然还有监听,这个东西写在程序入口比较好,至于为何下次讲
}
if (products.count == 0){
DLog(@"无法获取商品");
}
}
//查询失败后的回调
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
//菊花消失
DLog(@"请求苹果服务器失败%@",[error localizedDescription]);
}
下边就是监听结果了
//监听购买结果,每个状态下都要结束订单,否则就坑爹了
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {//当用户购买的操作有结果时,就会触发下面的回调函数,
//菊花消失
DLog(@"来监听购买结果吧");
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased://交易成功
[self completeTransaction:transaction];//验证
//如果用户在这中间退出,咋办??不知道的看下一篇,坑坑坑都是坑
DLog(@"结束订单了");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];//验证成功与否,咱们都注销交易,否则会出现虚假凭证信息一直验证不通过..每次进程序都得输入苹果账号的情况
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];//交易失败方法
break;
case SKPaymentTransactionStateRestored://已经购买过该商品
DLog(@"已经购买过");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];//消耗型不支持恢复,所以我就不写了
break;
case SKPaymentTransactionStatePurchasing:
DLog(@"已经在商品列表中");//菊花
break;
case SKPaymentTransactionStateDeferred:
DLog(@"最终状态未确定 ");
break;
default:
break;
}
}
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"购买失败");
} else {
NSLog(@"用户取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
下边就是和服务器验证凭证了,重点坑在这里
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
if (self.cash != nil) { //self.cash这玩意我是点击商品后传进来的,所以通过它判断是不是漏单的,有他的话就走正常流程
DLog(@"购买成功验证订单");
NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
NSString *a = [data base64EncodedStringWithOptions:0];
NSLog(@"base64JSONString =---%@---",a);//得到凭证
NSDictionary * dic = @{@"uid":用户id,
@"receipt":a,
还有后台要的其他东西,起码凭证和用户id是必须的吧};
DLog(@"验证信息%@",dic);
//存起来
[self addDicToPayAry:dic];//先把这个信息存起到本地,这是你埋坑的第一步,有空再解释
[self testForServer:dic];//和后台去二次验证,这个很必要
}
else
{
DLog(@"漏单流程从本地取凭证去验证");
[self checkUnTestReceipt];//从本地取凭证验证去,下篇分解
}//不是点击cell 进来的,也就是说上次订单没结束,日狗去吧
}
服务器交互,只要服务器有反应咱就把本地的信息移除,因为验证成功删了正常,验证不成功说明凭证有假(当然也可能苹果出问题,这咱管不了了)删,咱这边服务器出毛病(我们后台说这个状态是后台验证过了但是没有发商品,用户找客服),删.
只有没和自己后台交互上的时候,我们就在本地找村过的凭证,有的话说明有没验证过的,咱继续验证.没的话(第一次也不会没,因为存过一次但是就没删过)说明都验证过了.总之这个过程不管怎么样都要结束订单.
- (void)testForServer:(NSDictionary *)dic
{
WEAKSELF;
[[NetWorkManager sharedInstance]postJsonData:dic url:url successBlock:^(id responseBody) {
DLog(@"支付成功%@",responseBody);
if ([responseBody[@"code"]isEqualToNumber:@(200)]) {
DLog(@"1验证成功完成交易OKOKOKOK");
if (weakSelf.paySuccessBlock){
weakSelf.paySuccessBlock();//告诉外边UI做处理
}
[weakSelf removeDicFromPayAry:dic];//从本地移除凭证等信息dic
}
if (验证receipt-data失败) {
DLog(@"1验证receipt-data失败");
[weakSelf removeDicFromPayAry:dic];//移除
}
if (我们服务器有毛病) {
[weakSelf removeDicFromPayAry:dic];
DLog(@"1你去找客服吧我们服务器有毛病");
}
// DLog(@"我结束订单了-----");
} failureBlock:^(NSString *error) {
DLog(@"%@和自己服务器交互失败11",error);
[weakSelf checkUnTestReceipt];//和自己后台没联系上,所以要检查本地有没有存过的凭证,有的话继续验证.下篇详聊
}];
}
篇幅问题,存凭证删凭证和再次验证凭证下次说.
还有订单没结束的坑(用户买成功了苹果服务器不给力,用户急脾气关应用了,订单还没结束)等等,下回分解.
8.25更,来埋坑了,点进来↓
iOS内购第二篇
感觉还是不太完美啊,先这样吧
网友评论
概率:
我做的这个项目概率还是不小的,用户点击下单之后,app出现"正在加载"界面,现在的状态是只能和苹果进行支付交易,其他的是不能交互,正常来说请求网络和整个支付流程很开就完成了,但是遇到苹果服务器响应慢的时候,用户可能就要关闭这个交易,那用户就可能会关闭app.
方法:
说实话,我也没有太好的解决方法,
一.我现在是在 [[SKPaymentQueue defaultQueue] addPayment:payment];//发起购买之前 和 case SKPaymentTransactionStatePurchasing: DLog(@"已经在商品列表中");//菊花这步都保存数据(要通过orderId区分一下,不能存入同一个订单),然后1.如果苹果那边支付成功并且向自己的服务器验证成功的时候删除数据,2.如果用户取消支付的时候删除数据
二.在app 即将关闭的时候另一保存一份当前正在操作的这个订单数据, 另保存的这个数据用于- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction 中 transaction中取消支付或者支付失败的队列时候,从在[[SKPaymentQueue defaultQueue] addPayment:payment];这步之前保存数据中删除用
情况描述:在上线之前通过沙箱测试是没有问题的,但是上线之后,出现了问题,出现的问题是这样的,如果这个用户在购买之前apple ID 账号绑定这付款信息(支付宝或者是银行卡)是可以够买成功的,但是如果这个用户在购买之前没有绑定付款信息,他点击购买,会跳入iOS系统的绑定付款信息页面,然后支付成功,但是好像没有监听到这个状态 SKPaymentTransactionStatePurchased,
NSData *receiptData = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
NSString *transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];