1. 程式人生 > >iOS應用內購買專案(IAP)

iOS應用內購買專案(IAP)

iOS應用內購買專案流程,大致分為三個部分:

第一部分:iTunes connect設定,包含建立新的釋出版本號、建立購買專案(商品)、設定協議稅務銀行業務、新增沙盒技術測試賬號;

第二部分:在專案Xcode中設定允許In-App Purchase,然後編寫程式碼;程式碼部分可以使用原生態的,也可以使用第三方整合好的(IAPHelper),需要注意本地儲存交易憑證,用於網路不佳或失敗的情況下,重新提交。程式碼完成好之後,打包提交到APP Strore等待稽核,提交的同時也把之前的內購專案勾選一併提交稽核,待稽核通過就可以測試了(可能稽核通過之後需等待24小時,才能購買成功);

第三部分:測試是否成功購買,這裡需要真機測試,並登出本機的Apple ID,然後在app中點選購買的時候,輸入之前新增的沙盒測試賬號。這部分可能會報各種錯誤,可能是前面一二部分沒有設定完善,也有可能APP Store的問題,需要等待24小時自動就可以了,總之有問題就會有對應的解決方法,網上部落格的解決方法也會幫助你的。

前面的設定部分按照開發文件設定,這裡直接上程式碼:

//去蘋果伺服器請求商品
- (void)requestProductData:(NSString *)productId{
    NSSet* dataSet = [[NSSet alloc] initWithArray:@[productId]];
    [IAPShare sharedHelper].iap = [[IAPHelper alloc] initWithProductIdentifiers:dataSet];
#if DEBUG==1
    [IAPShare sharedHelper].iap.production = NO;
#else
    [IAPShare sharedHelper].iap.production = YES;
#endif
    // 請求商品資訊
    [[IAPShare sharedHelper].iap requestProductsWithCompletion:^(SKProductsRequest* request,SKProductsResponse* response){
         if(response.products.count > 0 ) {
             SKProduct *product = response.products[0];
             [[IAPShare sharedHelper].iap buyProduct:product onCompletion:^(SKPaymentTransaction* trans){
                 if(trans.error){
                     [self.hud hideAnimated:YES];
                     [MBProgressHUD showError:@"購買失敗,請重試" toView:self.view];
                }else if(trans.transactionState == SKPaymentTransactionStatePurchased) {
                    self.receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
                    [self justifyIAP:self.hud];
                }else if(trans.transactionState == SKPaymentTransactionStateFailed) {
                    if (trans.error.code == SKErrorPaymentCancelled) {
                        [self.hud hideAnimated:YES];
                    }else{
                        [[[CommonUIAlert alloc] init] showCommonAlertView:self title:@"" message:@"購買失敗,請重試" cancelButtonTitle:@"取消" otherButtonTitle:@"確定" cancle:^{
                            [self.hud hideAnimated:YES];
                            [self saveReceipt];
                        } confirm:^{
                            [self justifyIAP:self.hud];
                        }];
                    }
                }
            }];
         }else{
             [self.hud hideAnimated:YES];
             [MBProgressHUD showError:@"購買失敗,請重試" toView:self.view];
         }
     }];
}

#pragma mark - ios iap應用內支付二次驗證
- (void)justifyIAP:(MBProgressHUD*)hud{
    if(hud==nil){
        hud = [MBProgressHUD showMessag:@"支付中..." toView:self.view];
    }
    NSString *receiptBase64 = [NSString base64StringFromData:self.receipt length:[self.receipt length]];
    __weak typeof(self) weakSelf = self;
    NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
    [requestDict setObject:[DataModelInstance shareInstance].userModel.userId forKey:@"userid"];
    [requestDict setObject:[CommonMethod paramStringIsNull:receiptBase64] forKey:@"data"];
    [self requstType:RequestType_Post apiName:API_NAME_USER_POST_USER_IAPSECONDVALID paramDict:requestDict hud:hud success:^(AFHTTPRequestOperation *operation, id responseObject, MBProgressHUD *hud) {
        if([CommonMethod isHttpResponseSuccess:responseObject] == HttpResponseTypeSuccess){
            weakSelf.receipt = nil;
            [hud hideAnimated:YES];
            [weakSelf.navigationController popViewControllerAnimated:YES];
        }else{
            [[[CommonUIAlert alloc] init] showCommonAlertView:self title:@"" message:@"購買失敗,請重試" cancelButtonTitle:@"取消" otherButtonTitle:@"確定" cancle:^{
                [hud hideAnimated:YES];
                [weakSelf saveReceipt];
            } confirm:^{
                [weakSelf justifyIAP:hud];
            }];
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error, MBProgressHUD *hud) {
        [[[CommonUIAlert alloc] init] showCommonAlertView:self title:@"" message:@"購買失敗,請重試" cancelButtonTitle:@"取消" otherButtonTitle:@"確定" cancle:^{
            [hud hideAnimated:YES];
            [weakSelf saveReceipt];
        } confirm:^{
            [weakSelf justifyIAP:hud];
        }];
    }];
}

//持久化儲存使用者購買憑證(這裡最好還要儲存當前日期,使用者id等資訊,用於區分不同的憑證)
- (void)saveReceipt{
    if(self.receipt && self.receipt.length){
        NSString *fileName = [NSString genUUID];
        NSString *savedPath = [NSString stringWithFormat:@"%@%@.plist", AppStoreInfoLocalFilePath, fileName];
        NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys: self.receipt, IAP_RECEIPT,[DataModelInstance shareInstance].userModel.userId,IAP_USER_ID,nil];
        BOOL result = [dic writeToFile:savedPath atomically:YES];
        NSLog(@"%d",result);
        self.receipt = nil;
    }
}


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        
        
    //從伺服器驗證receipt失敗之後,在程式再次啟動的時候,使用儲存的receipt再次到伺服器驗證
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:AppStoreInfoLocalFilePath]) {//如果在改路下不存在檔案,說明就沒有儲存驗證失敗後的購買憑證,也就是說傳送憑證成功。
        [fileManager createDirectoryAtPath:AppStoreInfoLocalFilePath//建立目錄
               withIntermediateDirectories:YES
                                attributes:nil
                                     error:nil];
    }else{//存在購買憑證,說明發送憑證失敗,再次發起驗證
        [self sendFailedIapFiles];
    }
    return YES;
}

//驗證receipt失敗,App啟動後再次驗證
- (void)sendFailedIapFiles{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error = nil;
    
    //搜尋該目錄下的所有檔案和目錄
    NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:AppStoreInfoLocalFilePath error:&error];
    
    if (error == nil){
        for (NSString *name in cacheFileNameArray){
            if ([name hasSuffix:@".plist"]){//如果有plist字尾的檔案,說明就是儲存的購買憑證
                NSString *filePath = [NSString stringWithFormat:@"%@/%@", AppStoreInfoLocalFilePath, name];
                [self sendAppStoreRequestBuyPlist:filePath];
            }
        }
    }
}

- (void)sendAppStoreRequestBuyPlist:(NSString *)plistPath{
    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:plistPath];
    //這裡的引數請根據自己公司後臺伺服器介面定製,但是必須傳送的是持久化儲存購買憑證
    NSData *receipt = [dic objectForKey:IAP_RECEIPT];
    if(receipt==nil){
        [self sendAppStoreRequestSucceededWithData:plistPath];
        return;
    }
    NSString *receiptBase64 = [NSString base64StringFromData:receipt length:[receipt length]];
    NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
    [requestDict setObject:[dic objectForKey:IAP_USER_ID] forKey:@"userid"];
    [requestDict setObject:[CommonMethod paramStringIsNull:receiptBase64] forKey:@"data"];
    [[[UIViewController alloc] init] requstType:RequestType_Post apiName:API_NAME_USER_POST_USER_IAPSECONDVALID paramDict:requestDict hud:nil success:^(AFHTTPRequestOperation *operation, id responseObject, MBProgressHUD *hud) {
        if([CommonMethod isHttpResponseSuccess:responseObject] == HttpResponseTypeSuccess){
            [self sendAppStoreRequestSucceededWithData:plistPath];
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error, MBProgressHUD *hud) {
    }];
}

//驗證成功就從plist中移除憑證
- (void)sendAppStoreRequestSucceededWithData:(NSString *)plistPath{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:plistPath]){
        [fileManager removeItemAtPath:plistPath error:nil];
    }
}