KDIAPManager.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. //
  2. // KDIAPManager.m
  3. // KDChat
  4. //
  5. // Created by JYJ on 2019/6/14.
  6. // Copyright © 2019 dcjf. All rights reserved.
  7. //
  8. #import "KDIAPManager.h"
  9. #import <StoreKit/StoreKit.h>
  10. #import "KDMainPresenter.h"
  11. @interface KDIAPManager() <SKPaymentTransactionObserver,SKProductsRequestDelegate> {
  12. NSString *_productID;
  13. IAPCompletionHandle _handle;
  14. NSString *_orderId;
  15. }
  16. /** presenter */
  17. @property (nonatomic, strong) KDMainPresenter *presenter;
  18. @end
  19. @implementation KDIAPManager
  20. /**
  21. 单例模式
  22. @return HZIAPManager
  23. */
  24. + (instancetype)shareIAPManager {
  25. static KDIAPManager *IAPManager = nil;
  26. static dispatch_once_t onceToken;
  27. dispatch_once(&onceToken, ^{
  28. IAPManager = [[KDIAPManager alloc] init];
  29. });
  30. return IAPManager;
  31. }
  32. - (instancetype)init {
  33. if (self = [super init]) {
  34. [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
  35. }
  36. return self;
  37. }
  38. - (void)dealloc {
  39. [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
  40. }
  41. #pragma mark -- Method
  42. /** 检测客户端与服务器漏单情况处理*/
  43. + (void)checkOrderStatus {
  44. NSDictionary *orderInfo = [KDIAPManager getReceiptData];
  45. if (orderInfo != nil) {
  46. NSString *orderId = orderInfo[@"orderId"];
  47. NSString *receipt = orderInfo[@"receipt"];
  48. [[KDIAPManager shareIAPManager] verifyPurchaseForServiceWithOrderId:orderId receipt:receipt];
  49. }
  50. }
  51. #pragma mark -- 结束上次未完成的交易
  52. - (void)removeAllUncompleteTransactionsBeforeNewPurchase {
  53. NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
  54. if (transactions.count >= 1) {
  55. for (SKPaymentTransaction* transaction in transactions) {
  56. if (transaction.transactionState == SKPaymentTransactionStatePurchased ||
  57. transaction.transactionState == SKPaymentTransactionStateRestored) {
  58. [[SKPaymentQueue defaultQueue]finishTransaction:transaction];
  59. }
  60. }
  61. } else {
  62. NSLog(@"没有历史未消耗订单");
  63. }
  64. }
  65. - (void)startIAPWithOrderId:(NSString *)orderId productID:(NSString *)productID completeHandle: (IAPCompletionHandle)handle {
  66. _handle = handle;
  67. if(productID && productID.length > 0) {
  68. if ([SKPaymentQueue canMakePayments]) {
  69. [self removeAllUncompleteTransactionsBeforeNewPurchase];
  70. _orderId = orderId;
  71. // 允许内购
  72. _productID = productID;
  73. NSSet *set = [NSSet setWithObjects:productID, nil];
  74. SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
  75. request.delegate = self;
  76. // 获取内购项目信息
  77. [request start];
  78. } else {
  79. // 不允许内购
  80. [self handleActionWithType:IAPResultNotArrow data:nil];
  81. }
  82. } else {
  83. KDLog(@"内购项目ID错误");
  84. [self handleActionWithType:IAPResultIDError data:nil];
  85. }
  86. }
  87. - (void)handleActionWithType:(IAPResultType)type data:(NSData *)data{
  88. switch (type) {
  89. case IAPResultSuccess:
  90. KDLog(@"购买成功");
  91. break;
  92. case IAPResultFailed:
  93. KDLog(@"购买失败");
  94. break;
  95. case IAPResultCancle:
  96. KDLog(@"用户取消购买");
  97. break;
  98. case IAPResultVerFailed:
  99. KDLog(@"订单校验失败");
  100. break;
  101. case IAPResultVerSuccess:
  102. KDLog(@"订单校验成功");
  103. break;
  104. case IAPResultNotArrow:
  105. KDLog(@"不允许程序内付费");
  106. break;
  107. default:
  108. break;
  109. }
  110. if(_handle){
  111. _handle(type, data);
  112. }
  113. }
  114. #pragma mark -- SKProductsRequestDelegate
  115. /**
  116. 收到产品信息的回调
  117. @param request 请求的信息
  118. @param response 返回的产品信息
  119. */
  120. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
  121. // 商品所在数组
  122. NSArray *productArr = response.products;
  123. if (productArr.count > 0) {
  124. // SKProduct *product = nil;
  125. for (SKProduct *p in productArr) {
  126. if ([p.productIdentifier isEqualToString:_productID]) {
  127. // product = p;
  128. SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:p];
  129. payment.quantity = 1;
  130. // payment.applicationUsername
  131. // payment.applicationUsername = _orderId;
  132. // 发起内购
  133. [[SKPaymentQueue defaultQueue] addPayment:payment];
  134. break;
  135. }
  136. }
  137. } else {
  138. [self handleActionWithType:IAPResultIDError data:nil];
  139. }
  140. }
  141. #pragma mark -- SKPaymentTransactionObserver
  142. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
  143. // 获取结果
  144. // 验证成功与否都注销交易,否则会出现虚假凭证信息一直验证不通过,每次进程序都得输入苹果账号
  145. for (SKPaymentTransaction *trans in transactions) {
  146. switch (trans.transactionState) {
  147. case SKPaymentTransactionStatePurchasing:
  148. KDLog(@"商品添加进列表");
  149. break;
  150. case SKPaymentTransactionStatePurchased:
  151. KDLog(@"交易完成");
  152. [self completeTransaction:trans];
  153. [[SKPaymentQueue defaultQueue] finishTransaction:trans];
  154. break;
  155. case SKPaymentTransactionStateFailed:
  156. KDLog(@"交易失败");
  157. [self failedTransaction:trans];
  158. [[SKPaymentQueue defaultQueue] finishTransaction:trans];
  159. break;
  160. case SKPaymentTransactionStateRestored:
  161. KDLog(@"已经购买过商品");
  162. [[SKPaymentQueue defaultQueue] finishTransaction:trans]; //消耗型商品不用写
  163. break;
  164. case SKPaymentTransactionStateDeferred:
  165. break;
  166. default:
  167. break;
  168. }
  169. }
  170. }
  171. /**
  172. 内购完成
  173. @param transaction 内购项目体
  174. */
  175. - (void)completeTransaction:(SKPaymentTransaction *)transaction {
  176. NSString *productIdentifier = transaction.payment.productIdentifier;
  177. NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
  178. NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
  179. NSString *receiptString = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
  180. NSString *receiptString1 = [self encodeString:receiptString];
  181. if ([productIdentifier length] > 0 && receipt) {
  182. // 向自己的服务器验证购买凭证
  183. [self saveReceiptData:@{@"receipt":receiptString1,
  184. @"orderId":_orderId}];
  185. [self verifyPurchaseForServiceWithOrderId:_orderId
  186. receipt:receiptString1];
  187. } else {
  188. [self handleActionWithType:IAPResultVerFailed data:nil];
  189. }
  190. // // 自己向苹果发送验证
  191. // [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
  192. }
  193. - (NSString*)encodeString:(NSString*)unencodedString {
  194. // CharactersToBeEscaped = @":/?&=;+!@#$()~',*";
  195. // CharactersToLeaveUnescaped = @"[].";
  196. NSString *encodedString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
  197. (CFStringRef)unencodedString,
  198. NULL,
  199. (CFStringRef)@"!*'();:@&=+$,/?%#[]",
  200. kCFStringEncodingUTF8));
  201. return encodedString;
  202. }
  203. - (void)verifyPurchaseForServiceWithOrderId:(NSString *)orderId
  204. receipt:(NSString *)receiptString {
  205. if (orderId == nil && receiptString == nil) {
  206. [self handleActionWithType:IAPResultIDError data:nil];
  207. return;
  208. }
  209. NSMutableDictionary *param = [NSMutableDictionary dictionary];
  210. param[@"receipt-data"] = receiptString;
  211. param[@"order_no"] = orderId;
  212. #if DEBUG
  213. #endif
  214. [self.presenter requestGetIosVerifyWithParameters:param callBack:^(BOOL isSuccess, NSString * _Nonnull eMsg) {
  215. if (isSuccess) {
  216. [self handleActionWithType:IAPResultSuccess data:nil];
  217. [self removeLocReceiptData];
  218. } else {
  219. [self handleActionWithType:IAPResultVerFailed data:nil];
  220. }
  221. }];
  222. }
  223. - (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag {
  224. //交易验证
  225. NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
  226. NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
  227. NSError *error;
  228. NSDictionary *requestContents = @{@"receipt-data": [receipt base64EncodedStringWithOptions:0]};
  229. NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
  230. if (!requestData) { // 交易凭证为空验证失败
  231. [self handleActionWithType:IAPResultVerFailed data:nil];
  232. return;
  233. }
  234. //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
  235. //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
  236. NSString *serverString = @"https://buy.itunes.apple.com/verifyReceipt";
  237. if (flag) {
  238. serverString = @"https://sandbox.itunes.apple.com/verifyReceipt";
  239. }
  240. NSURL *storeURL = [NSURL URLWithString:serverString];
  241. NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
  242. [storeRequest setHTTPMethod:@"POST"];
  243. [storeRequest setHTTPBody:requestData];
  244. storeRequest.timeoutInterval = 20;
  245. [storeRequest addValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
  246. NSURLSession *session = [NSURLSession sharedSession];
  247. NSURLSessionDataTask *task = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  248. if (error) {
  249. // 无法连接服务器,购买校验失败
  250. [self handleActionWithType:IAPResultVerFailed data:nil];
  251. } else {
  252. NSError *error;
  253. NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
  254. if (!jsonResponse) {
  255. // 苹果服务器校验数据返回为空校验失败
  256. [self handleActionWithType:IAPResultVerFailed data:nil];
  257. }
  258. // 先验证正式服务器,如果正式服务器返回21007再去苹果测试服务器验证,沙盒测试环境苹果用的是测试服务器
  259. NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
  260. if (status && [status isEqualToString:@"21007"]) {
  261. [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];
  262. } else if(status && [status isEqualToString:@"0"]){
  263. [self handleActionWithType:IAPResultVerSuccess data:nil];
  264. }
  265. }
  266. }];
  267. [task resume];
  268. }
  269. /**
  270. 交易失败
  271. @param transaction 内购项目体
  272. */
  273. - (void)failedTransaction:(SKPaymentTransaction *)transaction {
  274. if (transaction.error.code != SKErrorPaymentCancelled) {
  275. [self handleActionWithType:IAPResultFailed data:nil];
  276. } else {
  277. [self handleActionWithType:IAPResultCancle data:nil];
  278. }
  279. }
  280. #pragma mark -- 本地保存一次支付凭证
  281. static NSString *const kSaveReceiptData = @"kSaveReceiptData";
  282. - (void)saveReceiptData:(NSDictionary *)receiptData {
  283. [[NSUserDefaults standardUserDefaults] setValue:receiptData forKey:kSaveReceiptData];
  284. [[NSUserDefaults standardUserDefaults]synchronize];
  285. }
  286. + (NSDictionary *)getReceiptData {
  287. return [[NSUserDefaults standardUserDefaults] valueForKey:kSaveReceiptData];
  288. }
  289. - (void)removeLocReceiptData {
  290. [[NSUserDefaults standardUserDefaults] removeObjectForKey:kSaveReceiptData];
  291. [[NSUserDefaults standardUserDefaults] synchronize];
  292. }
  293. - (KDMainPresenter *)presenter {
  294. if (!_presenter) {
  295. self.presenter = [[KDMainPresenter alloc] init];
  296. }
  297. return _presenter;
  298. }
  299. @end