1、內購流程
- 1、在 AppStore 中創建相應的物品,創建內購沙盒測試賬號
- 2、客戶端從後臺獲取相應的物品 ID (當然也可以再客戶端寫死,但後期擴展性就受限制了)
- 3、依據相應的物品 ID 請求商品的相關信息
- 4、依據商品信息創建訂單請求交易
- 5、依據返回的訂單狀態處理交易結果
- 6、請求後臺再次驗證訂單狀態
- 7、依據後臺返回結果處理相關邏輯
2、創建內購物品以及沙盒測試賬號
- 已經有朋友寫出了完善的教程,請參考如下鏈接,一步一步來就可以
3、客戶端編寫相關代碼
- 再這裏我把和支付相關的邏輯都抽取到了一個單例中,在最後貼上個人梳理的相關代碼大家一起學習
4、做內購過程中遇到的坑
-
1、內購沙盒測試賬號在支付成功後,再次購買相同 ID 的物品,會提示如下內容的彈窗。
您已購買此 App 內購買項目。此項目將免費恢復。
解決方法:在使用
[[SKPaymentQueue defaultQueue] addPayment:payment];
將支付信息添加進蘋果的支付隊列後,蘋果會自動完成後續的購買請求,在用戶購買成功或者點擊取消購買的選項後回調
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction;
方法返回響應的結果信息,在該方法內除了得到響應的支付信息編寫自身的業務的代碼外還要記得調用
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
方法通知蘋果的支付隊列該交易已經完成,否者就會已發起相同 ID 的商品購買就會有此項目將免費恢復的提示。
-
2、每次啓動一個新的內購支付流程,剛發起的時候系統就會調用
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction;
這個方法,結果擾亂一部分的支付業務邏輯在 SKPaymentQueue 被啓動並且添加了 addObserver之後,如果其判斷到有未完成的交易,會主動調用paymentQueue updatedTransactions 這個方法來繼續完成相關的交易流程,所以如果在上面那種情況下得到結果後不去調用 finish 接口,下次重新開啓支付流程就會檢查未完成的支付並調用該接口。
解決方法:
1.在得到支付結果後及時調用 finish 方法
2.添加一個是否是新發起的支付流程的條件,在條件符合的情況下才觸發應用的相關邏輯的代碼
(PS:在拿到蘋果的支付結果憑據的時候最好在客戶端做一份持久化的數據備份,等待後臺驗證完成後再清除掉,避免出現驗證中間出現問題導致用戶支付成功但後臺相關的增值處理沒有完成導致用戶金錢損失的問題)
-
3、如何區分購買物品的是 沙盒測試賬號 還是 真實賬號
後臺再驗證支付憑據的時候要區分是沙盒測試賬號購買的還是用戶真實賬號購買的,所以在傳憑據的時候還需要告訴後臺當前購買的賬號性質。
解決方法:通過在配置文件中定義相關的宏定義並結合 Debug 與 Release 的編譯環境確定相關的參數
// 蘋果內購是否爲沙盒測試賬號,打開就代表爲沙盒測試賬號,注意上線時註釋掉 #define APPSTORE_ASK_TO_BUY_IN_SANDBOX 1 // 生成訂單參數,注意沙盒測試賬號與線上正式蘋果賬號的驗證途徑不一樣,要給後臺標明 NSNumber *sandbox; #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG)) sandbox = @(0); #else sandbox = @(1); #endif
個人沒有找到相關的方法可以打完包後動態的檢測購買物品的賬號性質,希望知道的朋友分享一下,感謝 ^_^
5、iOS7 客戶端驗證的訂單狀態
-
蘋果在iOS7提升了購買憑據的安全性,可以直接單獨在客戶端完成訂單正確性的驗證,但是處於金錢考慮,購買完成後,建議還是要做憑據的後臺驗證工作。
#pragma mark 客戶端驗證購買憑據 - (void)verifyTransactionResult { // 驗證憑據,獲取到蘋果返回的交易憑據 // appStoreReceiptURL iOS7.0增加的,購買交易完成後,會將憑據存放在該地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 從沙盒中獲取到購買憑據 NSData *receipt = [NSData dataWithContentsOfURL:receiptURL]; // 傳輸的是BASE64編碼的字符串 /** BASE64 常用的編碼方案,通常用於數據傳輸,以及加密算法的基礎算法,傳輸過程中能夠保證數據傳輸的穩定性 BASE64是可以編碼和解碼的 */ NSDictionary *requestContents = @{ @"receipt-data": [receipt base64EncodedStringWithOptions:0] }; NSError *error; // 轉換爲 JSON 格式 NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error]; // 不存在 if (!requestData) { /* ... Handle error ... */ } // 發送網絡POST請求,對購買憑據進行驗證 NSString *verifyUrlString; #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG)) verifyUrlString = @"https://sandbox.itunes.apple.com/verifyReceipt"; #else verifyUrlString = @"https://buy.itunes.apple.com/verifyReceipt"; #endif // 國內訪問蘋果服務器比較慢,timeoutInterval 需要長一點 NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f]; [storeRequest setHTTPMethod:@"POST"]; [storeRequest setHTTPBody:requestData]; // 在後臺對列中提交驗證請求,並獲得官方的驗證JSON結果 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (connectionError) { NSLog(@"鏈接失敗"); } else { NSError *error; NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (!jsonResponse) { NSLog(@"驗證失敗"); } // 比對 jsonResponse 中以下信息基本上可以保證數據安全 /* bundle_id application_version product_id transaction_id */ NSLog(@"驗證成功"); } }]; }
6、內購驗證憑據返回結果狀態碼說明
-
蘋果反饋的狀態碼:
21000 App Store無法讀取你提供的JSON數據 21002 收據數據不符合格式 21003 收據無法被驗證 21004 你提供的共享密鑰和賬戶的共享密鑰不一致 21005 收據服務器當前不可用 21006 收據是有效的,但訂閱服務已經過期。當收到這個信息時,解碼後的收據信息也包含在返回內容中 21007 收據信息是測試用(sandbox),但卻被髮送到產品環境中驗證 21008 收據信息是產品環境中使用,但卻被髮送到測試環境中驗證
更爲詳細的信息請參考
7、如何恢復購買
- 注:此部分內容後期再詳細添加 ^_^
備註:
相關代碼:
XYPayManager.h
//
// XYPayManager.h
// xingyun
//
// Created by 鄭亞恆 on 15/11/2.
// Copyright © 2015年 鄭亞恆. All rights reserved.
//
#import <Foundation/Foundation.h>
// 蘋果內購是否爲沙盒測試賬號,打開就代表爲沙盒測試賬號,注意上線時註釋掉!!
#define APPSTORE_ASK_TO_BUY_IN_SANDBOX 1
typedef void(^payCompleteBlock)(NSDictionary *resultDic, BOOL isSuccess);
@interface XYPayManager : NSObject
+ (instancetype)sharedPayManager;
/// 蘋果內購
- (void)requestAppleStoreProductDataWithString:(NSString *)productIdentifier payComplete:(payCompleteBlock)payCompletionBlock;
/// 驗證蘋果支付訂單憑證
- (void)checkAppStorePayResultWithBase64String:(NSString *)base64String;
@end
XYPayManager.m
//
// XYPayManager.m
// xingyun
//
// Created by 鄭亞恆 on 15/11/2.
// Copyright © 2015年 鄭亞恆. All rights reserved.
//
#import "XYPayManager.h"
#import <StoreKit/StoreKit.h>
@interface XYPayManager() <SKPaymentTransactionObserver, SKProductsRequestDelegate>
// 蘋果內購
@property (nonatomic, copy) NSString *appleProductIdentifier;
@property (nonatomic, copy) payCompleteBlock payComplete;
@end
@implementation XYPayManager
+ (instancetype)sharedPayManager {
static XYPayManager *payManager;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{
payManager = [[XYPayManager alloc] init];
// 註冊蘋果內購
[[SKPaymentQueue defaultQueue] addTransactionObserver:payManager];
});
return payManager;
}
- (void)dealloc {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
#pragma mark - 蘋果支付充值
//請求商品
- (void)requestAppleStoreProductDataWithString:(NSString *)productIdentifier payComplete:(payCompleteBlock)payCompletionBlock {
if(![SKPaymentQueue canMakePayments]) {
NSLog(@"不允許程序內付費");
// [APPCONTEXT.hudHelper showHudOnWindow:@"不允許程序內付費" image:nil acitivity:NO autoHideTime:DEFAULTTIME];
return;
}
NSLog(@"-------------請求對應的產品信息----------------");
self.startBuyAppleProduct = YES;
self.payComplete = payCompletionBlock;
self.appleProductIdentifier = productIdentifier;
NSLog(@"生成產品信息");
NSArray *product = [[NSArray alloc] initWithObjects:productIdentifier, nil];
NSSet *nsset = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];
}
//收到產品返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSLog(@"--------------收到產品反饋消息---------------------");
NSArray *productArray = response.products;
if([productArray count] == 0){
NSLog(@"--------------沒有商品------------------");
return;
}
NSLog(@"productID:%@", response.invalidProductIdentifiers);
NSLog(@"產品付費數量:%lu",(unsigned long)[productArray count]);
SKProduct *product = nil;
for (SKProduct *pro in productArray) {
NSLog(@"%@", [pro description]);
NSLog(@"%@", [pro localizedTitle]);
NSLog(@"%@", [pro localizedDescription]);
NSLog(@"%@", [pro price]);
NSLog(@"%@", [pro productIdentifier]);
if([pro.productIdentifier isEqualToString:self.appleProductIdentifier]){
product = pro;
}
}
SKPayment *payment = [SKPayment paymentWithProduct:product];
NSLog(@"發送購買請求");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
//請求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
NSLog(@"------------------錯誤-----------------:%@", error);
}
- (void)requestDidFinish:(SKRequest *)request{
NSLog(@"------------反饋信息結束-----------------");
}
//監聽購買結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction {
for(SKPaymentTransaction *paymentTransactionp in transaction){
switch (paymentTransactionp.transactionState) {
case SKPaymentTransactionStatePurchased:
{
NSLog(@"交易完成-restoreCompletedTransactions");
/* your code */
[self buyAppleStoreProductSucceedWithPaymentTransactionp:paymentTransactionp];
[self completeTransaction:paymentTransactionp];
}
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品添加進列表");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"已經購買過商品");
break;
case SKPaymentTransactionStateFailed:
{
NSLog(@"交易失敗");
/* your code */
[self completeTransaction:paymentTransactionp];
}
break;
}
}
}
// 蘋果內購支付成功
- (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp {
/* 獲取相應的憑據,並做 base64 編碼處理 */
NSString *base64Str = [paymentTransactionp.transactionReceipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSLog(@"蘋果內購憑據號\n\n\n\n\n\n%@\n\n\n\n\n\n",base64Str);
[self checkAppStorePayResultWithBase64String:base64Str];
}
- (void)checkAppStorePayResultWithBase64String:(NSString *)base64String {
/* 生成訂單參數,注意沙盒測試賬號與線上正式蘋果賬號的驗證途徑不一樣,要給後臺標明 */
NSNumber *sandbox;
#if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
sandbox = @(0);
#else
sandbox = @(1);
#endif
NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];;
[prgam setValue:sandbox forKey:@"sandbox"];
[prgam setValue:base64String forKey:@"reciept"];
/*
請求後臺接口,服務器處驗證是否支付成功,依據返回結果做相應邏輯處理
*/
}
//交易結束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"交易結束");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
@end