iOS:蘋果內購實踐

iOS 蘋果的內購css

 

1、介紹api

    蘋果規定,凡是虛擬的物品(例如:QQ音樂的樂幣)進行交易時,都必須走蘋果的內購通道,蘋果要收取大約30%的抽成,因此不容許接入第三方的支付方式(微信、支付寶等),固然開發者能夠設置後門,在審覈時避開審覈人員。這個是有風險的,一旦發現,app會被當即下架,仍是老老實實接入內購吧。數組

 

2、注意安全

內購接入仍是比較簡單的,蘋果提供了專門的框架<StoreKit/StoreKit.h>,只要按照它提供的api進行開發就行。然而,接入的過程仍是有須要注意的地方,分別是:漏單處理、二次驗證、移除交易、遊客模式。微信

 

漏單處理: 這個是必定會存在的,由於用戶的一些誤操做,形成的漏單基本沒法避免,針對這種狀況,最終的處理方式就是人工客服。固然,這個過程是能夠優化的,開發者能夠進行存儲訂單票據,server存儲訂單號,本地存儲票據。若是用戶啓動app後,檢測到用戶上次付了款,可是須要的商品沒有給到用戶,此時能夠自動進行驗證並處理,驗證經過,就將商品補給用戶。app

 

二次驗證:這個步驟必不可少,首先正式環境驗證,若是驗證經過,說明是線上環境,能夠正常操做。若是驗證不經過,說明是沙盒環境,須要在沙盒環境下再次驗證,沙盒環境下的驗證結果會有一個統一的彈框標識[Environment : Sandbox],只要內購沒有上線,驗證時都是沙盒環境彈框。 二次驗證的這個過程能夠避免在審覈app時,由於沒有驗證經過直接被拒的風險。二次驗證放在server端實現,更加安全。框架

 

移除交易:用戶再次交易時,若是上次的交易沒有被移除,那麼這次的交易會一直在隊列中等候,沒法被提交,因此必定要在上次交易完成時移除交易。ide

 

遊客模式:若是咱們的app支持遊客使用,那麼這個內購就必需要求對遊客進行開放,不然審覈會被拒絕。測試

  

3、使用fetch

一、實現代理

@interface InAppPurchaseViewController ()<SKPaymentTransactionObserver,SKProductsRequestDelegate> @property (nonatomic, strong)NSMutableArray *products; @property (nonatomic, strong)Product *currentProduct; @property (nonatomic, copy)NSString *currentPayNo; @end
View Code

二、添加觀察者

-(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // 添加觀察者
 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; }
View Code

三、移除觀察者

-(void)viewWillDisappear:(BOOL)animated { // 移除觀察者
 [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; }
View Code

四、移除沒有關閉的交易並作漏單處理

#pragma mark 先檢查以前是否有未關閉的交易並作漏單處理
-(void)checkNotCloseAndFinishedTransaction{ NSArray* transactions = [SKPaymentQueue defaultQueue].transactions; for (SKPaymentTransaction* transaction in transactions) { if (transaction.transactionState == SKPaymentTransactionStatePurchased) { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } } NSArray *tickets = [InAppPurchaseTicketService fetchInAppPurchaseTicket]; if (tickets.count > 0) { [MBProgressHUD showMessage:@"正在驗證未處理的訂單,請稍後"]; self.conchChargeView.userInteractionEnabled = NO; for (InAppPurchaseTicket *ticket in tickets) { [self checkAppStorePayResultWithTikect:ticket]; } } }
View Code

五、用戶使用productId進行下單

-(void)loadPayNoData{ AppWeak(weakSelf, self); NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"productId"] = self.currentProduct.productId; [InAppPurchaseTicketService getIosOrderWithParams:params success:^(NSArray *items, BOOL isLocalData) { if (items.count >0 ) { InAppPurchaseInfo *inAppPurchaseInfo = items[0]; weakSelf.currentPayNo = inAppPurchaseInfo.payNo; [weakSelf startPayForProduct:weakSelf.currentProduct.productId]; } } failure:^(id errorInfo) { [self showErrorInfo:errorInfo]; }]; }
View Code

六、開始內購

-(void)startPayForProduct:(NSString *)productID{ if([SKPaymentQueue canMakePayments]){ [MBProgressHUD showMessage:@"正在請求商品信息,請稍等..."]; self.conchChargeView.userInteractionEnabled = NO; // productID就是你在建立購買項目時所填寫的產品ID
 [self requestProductID:productID]; }else{ // NSLog(@"不容許程序內付費");
        UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"舒適提示" message:@"請先開啓應用內付費購買功能。"
                                                            delegate:nil cancelButtonTitle:@"肯定" otherButtonTitles: nil]; [alertError show]; } }
View Code

七、請求全部的商品ID

-(void)requestProductID:(NSString *)productID{ // 1.拿到全部可賣商品的ID數組
    NSMutableArray *productIDArray = [NSMutableArray array]; for (Product *product in self.products) { [productIDArray addObject:product.productId]; } NSSet *sets = [[NSSet alloc] initWithArray:productIDArray]; // 2.向蘋果發送請求,請求全部可買的商品 // 2.1.建立請求對象
    SKProductsRequest *sKProductsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:sets]; // 2.2.設置代理(在代理方法裏面獲取全部的可賣的商品)
    sKProductsRequest.delegate = self; // 2.3.開始請求
 [sKProductsRequest start]; }
View Code

八、獲取蘋果那邊的內購監聽

//請求成功
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ NSArray *product = response.products; if([product count] == 0){ [MBProgressHUD hideHUD]; self.conchChargeView.userInteractionEnabled = YES; UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"沒有商品" delegate:nil cancelButtonTitle:@"關閉" otherButtonTitles:nil, nil]; [alertView show]; return; } for (SKProduct *sKProduct in product) { NSLog(@"SKProduct 描述信息:%@", sKProduct.description); NSLog(@"localizedTitle 產品標題:%@", sKProduct.localizedTitle); NSLog(@"localizedDescription 產品描述信息:%@",sKProduct.localizedDescription); NSLog(@"price 價格:%@",sKProduct.price); NSLog(@"productIdentifier Product id:%@",sKProduct.productIdentifier); if([sKProduct.productIdentifier isEqualToString:self.currentProduct.productId]){ [self buyProduct:sKProduct]; break; } } } //請求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{ [MBProgressHUD hideHUD]; self.conchChargeView.userInteractionEnabled = YES; UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"提示" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"關閉" otherButtonTitles:nil]; [alerView show]; }
View Code

九、建立票據,在隊列中等候處理

-(void)buyProduct:(SKProduct *)product{ // 1.建立票據
    SKPayment *skpayment = [SKPayment paymentWithProduct:product]; // 2.將票據加入到交易隊列
 [[SKPaymentQueue defaultQueue] addPayment:skpayment]; }
View Code

十、內購回調

#pragma mark 4.實現觀察者監聽付錢的代理方法,只要交易發生變化就會走下面的方法
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{ /* SKPaymentTransactionStatePurchasing, 正在購買 SKPaymentTransactionStatePurchased, 已經購買 SKPaymentTransactionStateFailed, 購買失敗 SKPaymentTransactionStateRestored, 回覆購買中 SKPaymentTransactionStateDeferred 交易還在隊列裏面,但最終狀態尚未決定 */

    for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing:{ [MBProgressHUD hideHUD]; [MBProgressHUD showMessage:@"正在購買中,別走開..."]; NSLog(@"正在購買..."); } break; case SKPaymentTransactionStatePurchased:{ // 購買後告訴交易隊列,把這個成功的交易移除掉
 [queue finishTransaction:transaction]; [MBProgressHUD hideHUD]; [self SavePaymentTransactionpAfterbuyAppleStoreProductSucceed:transaction]; NSLog(@"購買成功"); } break; case SKPaymentTransactionStateFailed:{ // 購買失敗也要把這個交易移除掉
 [queue finishTransaction:transaction]; [MBProgressHUD hideHUD]; self.conchChargeView.userInteractionEnabled = YES; NSString *errorInfo = @"購買失敗,請稍後從新購買"; if (transaction.error) { NSString *reason = transaction.error.userInfo[NSLocalizedFailureReasonErrorKey]; if ([StringUtility isStringNotEmptyOrNil:reason]) { errorInfo = reason; } } UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:errorInfo delegate:nil cancelButtonTitle:@"關閉" otherButtonTitles:nil, nil]; [alertView show]; NSLog(@"購買失敗"); } break; case SKPaymentTransactionStateRestored:{ // 回覆購買中也要把這個交易移除掉
 [queue finishTransaction:transaction]; [MBProgressHUD hideHUD]; self.conchChargeView.userInteractionEnabled = YES; UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"重複購買了" delegate:nil cancelButtonTitle:@"關閉" otherButtonTitles:nil, nil]; [alertView show]; NSLog(@"重複購買了"); } break; case SKPaymentTransactionStateDeferred:{ NSLog(@"交易還在隊列裏面,但最終狀態尚未決定"); } break; default: break; } } }
View Code

十一、本地存儲票據

// 蘋果內購支付成功
- (void)SavePaymentTransactionpAfterbuyAppleStoreProductSucceed:(SKPaymentTransaction *)paymentTransactionp { // 傳輸的是BASE64編碼的字符串 // 驗證憑據,獲取到蘋果返回的交易憑據 // appStoreReceiptURL iOS7.0增長的,購買交易完成後,會將憑據存放在該地址
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 從沙盒中獲取到購買憑據
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 傳輸的是BASE64編碼的字符串
    NSString *reciept = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; // productIdentifier
    NSString *productIdentifier = paymentTransactionp.payment.productIdentifier; if ([productIdentifier length]>0) { //本地存儲票據
        InAppPurchaseTicket *ticket = [[InAppPurchaseTicket alloc] init]; ticket.payNo = self.currentPayNo; ticket.productId = self.currentProduct.productId; ticket.reciept = reciept; ticket.state = 1; [InAppPurchaseTicketService saveLocalTransaction:ticket]; // 去驗證是否真正的支付成功了
        [MBProgressHUD showMessage:@"購買成功,正在驗證訂單..."]; [self checkAppStorePayResultWithTikect:ticket]; } }
View Code

十二、二次驗證

#pragma mark 服務端驗證購買憑據
- (void)checkAppStorePayResultWithTikect:(InAppPurchaseTicket *)tikect { /* 生成訂單參數,注意沙盒測試帳號與線上正式蘋果帳號的驗證途徑不同,要給後臺標明 注意: 本身測試的時候使用的是沙盒購買(測試環境) App Store審覈的時候也使用的是沙盒購買(測試環境) 上線之後就不是用的沙盒購買了(正式環境) 因此此時應該先驗證正式環境,在驗證測試環境 正式環境驗證成功,說明是線上用戶在使用 正式環境驗證不成功返回21007,說明是本身測試或者審覈人員在測試 蘋果AppStore線上的購買憑證地址是: https://buy.itunes.apple.com/verifyReceipt 測試地址是:https://sandbox.itunes.apple.com/verifyReceipt
     
     */ NSString *sandbox; #ifdef TEST sandbox = @"0"; //沙盒測試環境
#else sandbox = @"1"; //線上正式環境
#endif
    
    if (!tikect || !tikect.payNo || tikect.payNo.length==0 || !tikect.reciept || tikect.reciept.length==0) { return; } AppWeak(weakSelf, self); NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; params[@"payno"]   = tikect.payNo;               //訂單號
    params[@"sandbox"] = sandbox;                    //使用環境
    params[@"receipt"] = tikect.reciept;             //票據信息
 [InAppPurchaseTicketService doubleIosVerifyWithParams:params success:^(NSArray *items, BOOL isLocalData) { //隱藏loding
 [MBProgressHUD hideHUD]; [MBProgressHUD showSuccess:@"恭喜你,購買成功" afterDelay:2.0]; weakSelf.conchChargeView.userInteractionEnabled = YES; //清除本地當前對應訂單票據
        InAppPurchaseInfo *info = items[0]; [InAppPurchaseTicketService clearInAppPurchaseTicketWithPayNo:info.payNo]; //刷新UI
        if (self.fromVCType == FromVCTypeCurrentInAppPurchaseVC) { [weakSelf loadConchData]; } else{ //回調並跳轉頁面
            if (weakSelf.chargeConchSuccsssBlock) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ weakSelf.chargeConchSuccsssBlock(@(YES)); [weakSelf.navigationController popViewControllerAnimated:YES]; }); } } } failure:^(id errorInfo) { [MBProgressHUD hideHUD]; weakSelf.conchChargeView.userInteractionEnabled = YES; UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"驗證失敗" message:errorInfo delegate:nil cancelButtonTitle:@"關閉" otherButtonTitles:nil, nil]; [alertView show]; }]; }
View Code

1三、懶加載plist中的全部約定的商品productId

-(NSMutableArray *)products{ if (!_products) { _products = [NSMutableArray array]; NSString *path = [[NSBundle mainBundle] pathForResource:@"Products" ofType:@"plist"]; NSArray *items = [NSArray arrayWithContentsOfFile:path]; if ([ArrayUtility isArrayNotEmptyOrNil:items]) { for (NSDictionary *dic in items) { Product *product = [[Product alloc] initWithProperties:dic]; [_products addObject:product]; } } } return _products; }
View Code

 

3、推薦

最好把監聽回調和驗證寫在單例中,這樣app一啓動時,就能夠監聽回調狀態。

 

4、結論

這就是內購的所有流程了,我把主要的流程梳理了一下,具體的細節,開發人員本身去整理。與君共勉。。。。。

相關文章
相關標籤/搜索