#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SandBoxHelper : NSObject
+ (NSString *)homePath;
+ (NSString *)appPath;
+ (NSString *)docPath;
+ (NSString *)libPrefPath;
+ (NSString *)libCachePath;
+ (NSString *)tmpPath;
+ (NSString *)iapReceiptPath;
+(NSString *)SuccessIapPath;
+(NSString *)crashLogInfo;
+(NSString *)exitResourePath;
+(NSString *)tempOrderPath;
@end
NS_ASSUME_NONNULL_END
#import "SandBoxHelper.h"
@implementation SandBoxHelper
+ (NSString *)homePath {
return NSHomeDirectory();
}
+ (NSString *)appPath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
+ (NSString *)docPath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
+ (NSString *)libPrefPath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingFormat:@"/Preferences"];
}
+ (NSString *)libCachePath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingFormat:@"/Caches"];
}
+ (NSString *)tmpPath {
return [NSHomeDirectory() stringByAppendingFormat:@"/tmp"];
}
+ (NSString *)iapReceiptPath {
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/EACEF35FE363A75A"];
[self hasLive:path];
return path;
}
+ (BOOL)hasLive:(NSString *)path
{
if ( NO == [[NSFileManager defaultManager] fileExistsAtPath:path] )
{
return [[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:NULL];
}
return YES;
}
+(NSString *)SuccessIapPath{
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/SuccessReceiptPath"];
[self hasLive:path];
return path;
}
+(NSString *)exitResourePath{
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/ExitResourePath"];
[self hasLive:path];
return path;
}
+(NSString *)tempOrderPath{
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/tempOrderPath"];
[self hasLive:path];
return path;
}
+(NSString *)crashLogInfo{
NSString * path = [[self libPrefPath]stringByAppendingFormat:@"/crashLogInfoPath"];
[self hasLive:path];
return path;
}
@end
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^PayResult)(BOOL isSuccess, NSString *__nullable certificate,NSString * __nullable errorMsg);
@interface IPAPurchase : NSObject
@property (nonatomic, copy) PayResult payResultBlock;
+(instancetype)manager;
-(void)startManager;
-(void)stopManager;
-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult;
@property (nonatomic, copy) NSString * orderNumber;
@property (nonatomic, copy) NSString * userid;
@property (nonatomic, assign) CGFloat tbCount;
@end
NS_ASSUME_NONNULL_END
static NSString *const receiptKey = @"receiptKey"
dispatch_queue_t iap_queue(){
static dispatch_queue_t as_iap_queue
static dispatch_once_t onceToken_iap_queue
dispatch_once(&onceToken_iap_queue, ^{
as_iap_queue = dispatch_queue_create("com.iap.queue", DISPATCH_QUEUE_CONCURRENT)
})
return as_iap_queue
}
@interface IPAPurchase()
<SKPaymentQueueDelegate, SKProductsRequestDelegate>
/**
*
*/
@property (nonatomic, strong) SKProductsRequest * request
/**
*购买凭证(存储base64编码的交易凭证)
*/
@property (nonatomic, copy) NSString * receipt
/**
*
*/
@property (nonatomic, copy) NSString * productId
@end
static IPAPurchase *manager = nil
@implementation IPAPurchase
+(instancetype)manager{
static dispatch_once_t onceToken
dispatch_once(&onceToken, ^{
if(!manager){
manager = [[IPAPurchase alloc] init]
}
})
return manager
}
-(void)startManager{
dispatch_sync(iap_queue(), ^{
[[SKPaymentQueue defaultQueue] addTransactionObserver:manager]
})
}
-(void)stopManager{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self]
})
}
-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult{
self.payResultBlock = payResult
[self removeAllUncompleteTransactionBeforeStartNewtransaction]
//购买中HUD。。。
[self showWhiteHUDWithText:@" 购买中... "]
self.productId = productID
if(!self.productId.length){
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"没有对应的商品" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil]
[alertView show]
}
if([SKPaymentQueue canMakePayments]){
[self requestProductInfo:self.productId]
}
else{
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"请先开启应用内付费购买功能。" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil]
[alertView show]
}
}
-(void)removeAllUncompleteTransactionBeforeStartNewtransaction{
NSArray *transactionsArray = [SKPaymentQueue defaultQueue].transactions
if(transactionsArray.count >0){
//检测是否有未完成的交易
SKPaymentTransaction *transaction = [transactionsArray firstObject]
if(transaction.transactionState == SKPaymentTransactionStatePurchased){
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]
return
}
}
}
-(void)requestProductInfo:(NSString *)productId{
NSArray *productArray = [[NSArray alloc] initWithObjects:productId, nil]
NSSet *IDSet = [NSSet setWithArray:productArray]
self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:IDSet]
self.request.delegate = self
[self.request start]
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
// [self dismissCustom]
NSArray *myProduct = response.products
if(myProduct.count == 0){
//没有该商品信息HUD。。。
[self showErrorText:@"无法获取产品信息,购买失败"]
if (self.payResultBlock) {
self.payResultBlock(NO, nil, @"无法获取产品信息,购买失败")
}
return
}
SKProduct *product = nil
for (SKProduct *pro in myProduct) {
NSLog(@"SKProduct 描述信息%@", [pro description])
NSLog(@"产品标题 %@" , pro.localizedTitle)
NSLog(@"产品描述信息: %@" , pro.localizedDescription)
NSLog(@"价格: %@" , pro.price)
NSLog(@"Product id: %@" , pro.productIdentifier)
if([pro.productIdentifier isEqualToString:self.productId]){
product = pro
break
}
}
if(product){
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]
payment.applicationUsername = self.orderNumber
[[SKPaymentQueue defaultQueue] addPayment:payment]
}
else{
NSLog(@"没有此商品")
}
}
//查询失败后的回调
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
[self dismissCustom]
if (self.payResultBlock) {
self.payResultBlock(NO, nil, [error localizedDescription])
}
}
////如果没有设置监听购买结果将直接跳至反馈结束
-(void)requestDidFinish:(SKRequest *)request{
// [self dismissCustom]
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
//当用户购买的操作有结果时,就会触发下面的回调函数,
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:{
[self completeTransaction:transaction]
// [self dismissCustom]
}break
case SKPaymentTransactionStateFailed:{
[self failedTransaction:transaction]
[self dismissCustom]
}break
case SKPaymentTransactionStateRestored:{//已经购买过该商品
[self restoreTransaction:transaction]
[self dismissCustom]
}break
case SKPaymentTransactionStatePurchasing:{
NSLog(@"正在购买中...")
}break
case SKPaymentTransactionStateDeferred:{
NSLog(@"最终状态未确定")
[self dismissCustom]
}break
default:
break
}
}
}
//完成交易
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"购买成功,准备验证发货")
[self getReceipt]
[self saveReceipt:transaction]
[self checkIAPFiles:transaction]
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
NSString *error = nil
if(transaction.error.code != SKErrorPaymentCancelled) {
//购买失败HUD。。。
[self showSuccessText:@"购买失败"]
} else {
//取消购买HUD。。。
[self showSuccessText:@"取消购买"]
}
if (self.payResultBlock) {
self.payResultBlock(NO, nil, error)
}
[[SKPaymentQueue defaultQueue]finishTransaction:transaction]
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]
}
-(void)getReceipt{
NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL]
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl]
NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]
self.receipt = base64String
}
-(void)saveReceipt:(SKPaymentTransaction *)transaction{
NSString * userId
NSString * order
if (self.userid) {
userId = self.userid
[[NSUserDefaults standardUserDefaults]setObject:userId forKey:@"unlock_iap_userId"]
}else{
userId = [[NSUserDefaults standardUserDefaults]objectForKey:@"unlock_iap_userId"]
}
order = transaction.payment.applicationUsername
NSString *fileName = [NSString UUID]
NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName]
NSMutableDictionary * dic = [[NSMutableDictionary alloc]init]
[dic setValue: self.receipt forKey:receiptKey]
[dic setValue: userId forKey:@"user_id"]
[dic setValue: order forKey:@"order"]
BOOL ifWriteSuccess = [dic writeToFile:savedPath atomically:YES]
if (ifWriteSuccess) {
NSLog(@"购买凭据存储成功!")
}
}
-(void)checkIAPFiles:(SKPaymentTransaction *)transaction{
NSFileManager *fileManager = [NSFileManager defaultManager]
NSError *error = nil
NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error]
if (error == nil) {
for (NSString *name in cacheFileNameArray) {
if ([name hasSuffix:@".plist"]){ //如果有plist后缀的文件,说明就是存储的购买凭证
NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name]
[self sendAppStoreRequestBuyPlist:filePath trans:transaction]
}
}
} else {
}
}
-(void)SaveIapSuccessReceiptDataWithReceipt:(NSString *)receipt Order:(NSString *)order UserId:(NSString *)userId{
NSMutableDictionary * mdic = [[NSMutableDictionary alloc]init]
[mdic setValue:[self getCurrentZoneTime] forKey:@"time"]
[mdic setValue: order forKey:@"order"]
[mdic setValue: userId forKey:@"userid"]
[mdic setValue: receipt forKey:receiptKey]
NSString *fileName = [NSString UUID]
NSString * successReceiptPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper SuccessIapPath], fileName]
//存储购买成功的凭证
[mdic writeToFile:successReceiptPath atomically:YES]
}
-(NSString *)getCurrentZoneTime{
NSDate * date = [NSDate date]
NSDateFormatter*formatter = [[NSDateFormatter alloc]init]
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]
NSString*dateTime = [formatter stringFromDate:date]
return dateTime
}
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath trans:(SKPaymentTransaction *)transaction{
// [self showWhiteHUDWithText:@"验证中..."]
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath]
NSString * receipt = [dic objectForKey:receiptKey]
NSString * order = [dic objectForKey:@"order"]
NSString * userId = [dic objectForKey:@"user_id"]
WeakSelf
[JHNetworkHelper requestPOST:@"iapPay/yz_iappay" parameters:@{@"orderNo":order,@"receipt":receipt} modelClass:nil success:^(id responseObject) {
[weakSelf showSuccessText:@"购买成功"]
weakSelf.tbCount = [responseObject[@"result"][@"money"] floatValue]
[[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"]
NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]
NSString *result = [data base64EncodedStringWithOptions:0]
if (weakSelf.payResultBlock) {
weakSelf.payResultBlock(YES, result, nil)
}
//这里将成功但存储起来
[weakSelf SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId]
[weakSelf successConsumptionOfGoodsWithOrder:order]
} failure:^(NSError *error) {
[weakSelf dismissCustom]
}]
// [[ULSDKAPI shareAPI] sendVertifyWithReceipt:receipt order:order success:^(ULSDKAPI *api, id responseObject) {
//
// if (RequestSuccess) {
//
// NSLog(@"服务器验证成功!")
//
// [[SKPaymentQueue defaultQueue]finishTransaction:transaction]
//
// [RRHUD hide]
//
// [RRHUD showSuccessWithContainerView:UL_rootVC.view status:NSLocalizedString(@"购买成功", @"")]
//
// [[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"]
// NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]
//
// NSString *result = [data base64EncodedStringWithOptions:0]
//
// if (self.payResultBlock) {
// self.payResultBlock(YES, result, nil)
// }
// //这里将成功但存储起来
// [self SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId]
//
// [self successConsumptionOfGoodsWithOrder:order]
//
// }else{
// //在这里向服务器发送验证失败相关信息
// } failure:^(ULSDKAPI *api, NSString *failure) {
//
// }
}
-(void)successConsumptionOfGoodsWithOrder:(NSString * )cpOrder{
NSFileManager *fileManager = [NSFileManager defaultManager]
NSError * error
if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
NSArray * cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error]
if (error == nil) {
for (NSString * name in cacheFileNameArray) {
NSString * filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name]
[self removeReceiptWithPlistPath:filePath ByCpOrder:cpOrder]
}
}
}
}
-(void)removeReceiptWithPlistPath:(NSString *)plistPath ByCpOrder:(NSString *)cpOrder{
NSFileManager *fileManager = [NSFileManager defaultManager]
NSError * error
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath]
NSString * order = [dic objectForKey:@"order"]
if ([cpOrder isEqualToString:order]) {
//移除与用户id订单号一样的plist 文件
BOOL ifRemove = [fileManager removeItemAtPath:plistPath error:&error]
if (ifRemove) {
NSLog(@"成功订单移除成功")
}else{
NSLog(@"成功订单移除失败")
}
}else{
NSLog(@"本地无与之匹配的订单")
}
}
@end