最近应公司要求,集成iap支付,由于之前没做过,公司就我一个iOS,所以遇见不少坑,最后终于写完了,鉴于网上代码多的是缺胳膊少腿,所以我来一份全的,无废话的. 包括服务器验证,检验漏单的两种方式的.希望可以帮助到后续的伙伴们.
In-App-Purchase
首先、 先熟悉一下整个iap支付的基本流程
1. 程序向服务器发送请求,获得一份产品列表。
2. 服务器返回包含产品标识符的列表。 // 就是获得产品ID < productID>
3. 程序向App Store发送请求,得到产品的信息。
4. App Store返回产品信息。
5. 程序把返回的产品信息显示给用户(App的store界面)
6. 用户选择某个产品
7. 程序向App Store发送支付请求
8. App Store处理支付请求并返回交易完成信息。
9. 程序从信息中获得数据,并发送至服务器。
10. 服务器纪录数据,并进行审(我们的)查。
11. 服务器将数据发给App Store来验证该交易的有效性。
12. App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。
13. 服务器读取返回的数据,确定用户购买的内容。
14. 服务器将购买的内容传递给程序。
其次、你需要在开发者中--Itunes Connect ---我的App-----创建新应用----填写计费点,产品ID<功能-内部付费>
-- 账户与职能----创建沙盒测试账号 // <由于这部分内容比较简单不赘述>
然后, 就是iOS端的代码开发了
第一步:
1、 在工程中引入 storekit.framework 和 #import <StoreKit/StoreKit.h>
2、 获得所有的付费Product ID列表。这个可以用常量存储在本地,也可以由自己的服务器返回。
代码: 因为我是接着别人的代码写,之前用ASIHttpRequest 进行网络请求,所以我也继承了,你们用AFN,就用AFN
#import "BPIAPPurchaseClass.h"
#import <StoreKit/StoreKit.h>
#import "ASIHTTPRequest.h"
#import "BPOrderInfo.h"
#import "MBProgressHUD.h"
@interface BPIAPPurchaseClass ()<SKProductsRequestDelegate,SKPaymentTransactionObserver,MBProgressHUDDelegate,ASIHTTPRequestDelegate,HttpRequestBaseDelegate>
{
NSString* BPyInAppIdentifier;
BPOrderInfo *orderInfoPlist; // 订单Model
UIActivityIndicatorView* spinner;
NSArray *products; // 商品Array
NSArray *orderInfoArray;
MBProgressHUD *hud;
ASINetworkQueue *RequestQueue;
id<HttpRequestBaseDelegate> delegate;
SKPaymentTransaction *transactionList;
NSString * money;
SKProduct *skProduct;
NSArray *productArray;
NSDictionary *productDic;
}
@end
-(void)dealloc
{
[orderInfoPlist release];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[super dealloc];
}
- (instancetype)init
{
self = [super init];
if (self) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
+(BPIAPPurchaseClass *)SharedBPApplePayPlatform{
static BPIAPPurchaseClass *applePayClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
applePayClass = [[BPIAPPurchaseClass alloc]init];;
});
return applePayClass;
}
// 加载菊花
-(void)showBPProgressHUD
{
UIWindow* window = [UIApplication sharedApplication].keyWindow;
if (!window)
{
window = [[UIApplication sharedApplication].windows lastObject];
}
NSLog(@"rootViewController = %@",window.rootViewController);
hud = [[MBProgressHUD alloc]initWithView:window.rootViewController.view];
hud.center = window.center;
[window.rootViewController.view addSubview:hud];
hud.color = [UIColor blackColor];
// hud.alpha = 0.5;
hud.delegate = self;
hud.removeFromSuperViewOnHide = YES;
hud.labelText = @"正在请求数据,请稍候";
[hud show:YES];
[hud release];
}
//请求数据,也可以直接在按钮里调用,传过来 product_id
-(void)ApplePurchaseWithProductInfo:(BPOrderInfo *)orderInfo
{
BPyInAppIdentifier = orderInfo.productId;
orderInfoPlist = orderInfo;
// [self showBPProgressHUD]; //加载菊花打开
NSArray *array = @[BPyInAppIdentifier];
if ( [SKPaymentQueue canMakePayments]) {
NSSet *set = [NSSet setWithArray:array];
SKProductsRequest * appStoreRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
appStoreRequest.delegate = self;
[appStoreRequest start];
NSLog(@"允许应用内支付");
}else{
NSLog(@"不允许应用内支付");
}
}
//判断是否可以应用内支付
- (BOOL) canInAppPayment
{
//你应该根据是否想让越狱的iOS设备允许内支付
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"]){
NSLog(@"越狱版设备!");
return NO;
}
if ([SKPaymentQueue canMakePayments]) {
return YES;
} else {
return NO;
}
}
#pragma mark -SKProductsRequestDelegate 获取appstroe产品信息
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(@"@--------------收到appStore返回产品信息");
NSArray *gameProduct = response.products;
if (gameProduct.count == 0) {
NSLog(@"无法获取产品信息,购买失败");
return;
}
for (SKProduct *product in gameProduct) {
money = [NSString stringWithFormat:@"%@",product.price];
NSLog(@"@-------money = %@-------收到appStore返回产品信息",money);
NSLog(@"产品id==%@",response.invalidProductIdentifiers);
NSLog(@"产品数量==========%lu",(unsigned long)gameProduct.count);
NSLog(@"产品标题 %@ ", product.localizedTitle);
NSLog(@"产品描述信息: %@ ", product.localizedDescription);
NSLog(@"价格: %@" , product.price);
NSLog(@"Product id: %@ ", product.productIdentifier);
SKMutablePayment *Mpayment = [SKMutablePayment paymentWithProduct:product];
if ([[SKPaymentQueue defaultQueue] respondsToSelector:@selector(addPayment:)]) {
[[SKPaymentQueue defaultQueue] addPayment:Mpayment]; //请求已经生效
NSLog(@"请求生效");
}
}
[request release];
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"-------------------支付完成-----transaction=%@------------",transaction.transactionIdentifier);
// [hud hide:YES];
orderInfoPlist.orderId = transaction.transactionIdentifier;
NSString * productIdentifier = transaction.payment.productIdentifier;
if ([productIdentifier length]>0) {
// 向自己的服务器验证购买凭证
[self verifyReceiptFromCompanyServer:transaction]; //这个方法是内支付最重要的方法
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
//处理错误
// [hud hide:YES];
[request cancel];
request = nil;
}
// 交易失败,通知IAP进行UI刷新
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"购买失败");
[BPCustomPromptBox showWithTitle:[BPLanguage getStringForKey:@"支付请求失效" InTable:@"BPMultiLanguage"] AndDisappearSecond:2];
} else {
NSLog(@"用户取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// 二次验证的过程
//向服务器请求求验证
// Send the Receipt Data to the App Store
#pragma amrk --- 注意iOS9的session请求
- (void)verifyReceiptFromCompanyServer:(SKPaymentTransaction *)transaction
{
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle] appStoreReceiptURL]];//苹果推荐
if (!urlRequest) { /* ... error ... */
return;
}
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// NSLog(@"二次验证的data ====== %@",data);
NSLog(@"这是iap支付的二次验证流程");
NSString *base64String = [ASIHTTPRequest base64forData:data];
// NSDictionary *receiptDictionary = @{@"receipt-data":base64String};
//
// NSLog(@"二次验证的receiptDictionary ====== %@",receiptDictionary);
//
// NSData *Receivedata = [NSJSONSerialization dataWithJSONObject:receiptDictionary options:NSJSONWritingPrettyPrinted error:&error];
// NSLog(@"二次验证的提交验证的数据data ====== %@",Receivedata);
if(!orderInfoPlist.callBackUrl)
{
orderInfoPlist.callBackUrl = @"0";
}
NSMutableDictionary *dicc = [NSMutableDictionary dictionary];
[dicc setValue:base64String forKey:@"receipt"];
[dicc setValue:money forKey:@"money"];
[dicc setValue:[NSString stringWithFormat:@"%d",orderInfoPlist.ServerId] forKey:@"serverId"];
[dicc setValue:version forKey:@"version"];
[dicc setValue:[NSString stringWithFormat:@"%d",(int)[[NSDate date] timeIntervalSince1970]] forKey:@"time"];
[dicc setValue:[BPUserPreferences CurrentChannelId] forKey:@"channelId"];
[dicc setValue:transaction.transactionIdentifier forKey:@"orderId"];
NSString *str_sing = [BPUtility md5String:[NSString stringWithFormat:@"%@&key=jM2ilRIsu9dhDOwH",[self getHttpSing:dicc]]];
// NSString *str = [self getHttpSing:dicc];
//
// NSLog(@"排序后的字符串 = %@",str);
NSLog(@"dic.count = %ld",(unsigned long)dicc.count);
#pragma mark ----------- 交易信息本地化---------------------
NSString *library = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
library = [library stringByAppendingFormat:@"/Caches"];
NSFileManager *manger = [NSFileManager defaultManager];
NSString *path2 = [library stringByAppendingPathComponent:@"signReceipt"];
NSString *path3 = [library stringByAppendingPathComponent:@"orderInfo"];
[manger createDirectoryAtPath:path2 withIntermediateDirectories:YES attributes:nil error:nil]; // 签名文件夹
NSString *signPath = [NSString stringWithFormat:@"%@/%@",path2,@"sign.txt"];
NSMutableArray *SignArray = [NSMutableArray array];
[SignArray addObject:str_sing];
[SignArray writeToFile:signPath atomically:YES];
NSLog(@"本地化签名信息path = %@",signPath);
[manger createDirectoryAtPath:path3 withIntermediateDirectories:YES attributes:nil error:nil]; // 签名文件夹
NSString *orderPath = [NSString stringWithFormat:@"%@/%@",path3,@"orderInfo.txt"];
[dicc writeToFile:orderPath atomically:YES];
NSLog(@"本地化用户信息path = %@",orderPath);
// NSLog(@"dicc==== %@",signDic);
BPHttpRequestBase *httpBase = [[BPHttpRequestBase alloc]init];
ASIFormDataRequest *receiptRequest = [httpBase CreateRequestWithUrl:@"服务器二次验证url" SetInfoTag:@"IapReceiptVerify"];
httpBase.delegate = self;
if (!receiptRequest)
{
[receiptRequest cancel];
receiptRequest = nil;
}
[receiptRequest setRequestMethod:@"POST"];
[receiptRequest addPostValue:@"webpay" forKey:@"action"];
[receiptRequest addPostValue:@"ios" forKey:@"mode"];
[receiptRequest addPostValue: str_sing forKey:@"sign"];
[receiptRequest addPostValue:[NSString stringWithFormat:@"%d",(int)[[NSDate date] timeIntervalSince1970]] forKey:@"time"];
[receiptRequest addPostValue:[BPUtility macaddress] forKey:@"mac"];
[receiptRequest addPostValue:transaction.transactionIdentifier forKey:@"orderId"]; //**订单号
[receiptRequest addPostValue:@"verifyReceipt" forKey:@"method"];
[receiptRequest addPostValue:money forKey:@"money"]; // 产品的钱数
[receiptRequest addPostValue:base64String forKey:@"receipt"];
receiptRequest.delegate = self;
[httpBase.RequestQueue addOperation:receiptRequest];
[httpBase.RequestQueue go];
[receiptRequest startAsynchronous]; //异步请求序列
}];
[task resume];
}
*/
-(void)requestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
if (![request responseString]) {
return ;
}
NSLog(@"request.url== %@-----二次验证结果----responseString= %@",request.url,responseString);
NSDictionary *userInfo = request.userInfo;
[BPQLoadingView hideWithAnimated:NO];
if([[userInfo objectForKey:@"RequestTag"] isEqualToString:@"IapReceiptVerify"])
{
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[request responseData] options:NSJSONReadingMutableContainers error:nil];
NSLog(@"是否漏单服务器返回验证结果jsonDic = %@",dic);
int response = [[dic objectForKey:@"result"] intValue];
NSString *product_id = [dic objectForKey:@"product_id"];
// iap二次验证成功,支付成功
if(response == 0)
{
NSLog(@"支付验证成功,无漏单");
}
if(response == 21003)
{
NSLog(@"警告:出现漏单啦!!!");
// BPOrderInfo *orderInfo = [[BPOrderInfo alloc]init];
// orderInfo.productId = product_id;
// [[BPIAPPurchaseClass SharedBPApplePayPlatform] ApplePurchaseWithProductInfo:orderInfo];
// [orderInfo release];
NSLog(@"漏单的产品ID = %@",product_id);
}
if (response == -1) {
NSLog(@"支付签名错误");
}if (response == -3){
NSLog(@"订单已经到账");
}
}
}
#pragma mark - 购买商品
- (void)buyProduct:(SKProduct *)product
{
// 1.创建票据
SKPayment *payment = [SKPayment paymentWithProduct:product];
// 2.将票据加入到交易队列中
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
//出错处理
// [hud hide:YES];
}
#pragma mark-SKPayment TransactionObserver支付结果
//----监听购买结果
//然后当用户输入正确的appStore帐号密码后进入(再次说明 如果是测试 必须是你注册的测试账号 不能使用真实的AppleID )
//方法在新交易被创建或更新时都会被调用
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: //交易完成
NSLog(@"交易完成transactionIdentifier= %@", transaction.transactionIdentifier);
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed://交易失败
[self failedTransaction:transaction];
// [BPCustomPromptBox showWithTitle:[BPLanguage getStringForKey:@"交易失败, 请重试" InTable:@"BPMultiLanguage"] AndDisappearSecond:2];
NSLog(@"交易失败");
break;
case SKPaymentTransactionStateRestored://已经购买过该商品
[self restoreTransaction:transaction];
NSLog(@"已买过商品");
break;
case SKPaymentTransactionStatePurchasing: //商品添加进列表
NSLog(@"商品添加进列表");
break;
default:
// [hud hide:YES];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
{
NSLog(@"---------------移除商品-------------");
// [hud hide:YES];
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
NSLog(@"---------------重复支付失败-------------");
}
-(void)restoreTransaction: (SKPaymentTransaction *)transaction
{
NSLog(@"---------------重复支付-----------------");
// 对于已经购买的产品,恢复其处理逻辑
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)requestDidFinish:(SKRequest *)request {
NSLog(@"请求结束");
// [hud hide:YES];
}
// 签名字典排序
- (NSString *)getHttpSing:(NSMutableDictionary *)dic
{
NSString *str = nil;
NSMutableArray *parameters_array = [NSMutableArray arrayWithArray:[dic allKeys]];
[parameters_array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2];
//return [obj2 compare:obj1];//降序
} ];
for (int i = 0; i<parameters_array.count; i++) {
NSString *key = [parameters_array objectAtIndex: i];
NSString * value = [dic objectForKey:key];
if (i==0) {
str = [NSString stringWithFormat:@"%@=%@",key,value] ;
}else{
str = [NSString stringWithFormat:@"%@&%@=%@",str,key,value];
}
}
return str;
}
楼主博客技术支持QQ: 1428542683