因為之前做的項目都是電商的,交易的也都是實體的,所以都會采用微信,支付寶等第三方支付,現在這個公司的項目是個付費小說的軟件,要搞支付只能選擇蘋果內購了。
具體怎么在ituens connect開通就不在介紹了,網上也有很多資料,這篇文章主要分享一下app接入內購的代碼,已經防丟單的處理策略。
下面先介紹一下 內購的主要流程
- 首先拿到productid去請求蘋果服務器,請求產品信息
- 得到蘋果服務器的返回信息,如果產品存在且為可售狀態,則生成內購訂單,加入到蘋果的交易隊列
- 蘋果檢查到支付成功或者失敗后,會通過代理,回調支付的結果
- 根據結果,如果成功則取出交易憑證,一般是交給自己的服務器去驗證
- 驗證完成之后的一些處理
// 去蘋果服務器請求產品信息
- (void)requestProductData:(NSString *)productId {
NSArray *productArr = [[NSArray alloc]initWithObjects:productId, nil];
NSSet *productSet = [NSSet setWithArray:productArr];
SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:productSet];
request.delegate = self;
[request start];
#pragma mark - SKProductsRequestDelegate //這個是向蘋果服務器請求產品的代理
- (void)requestDidFinish:(SKRequest *)request
{
_timeIntervalStart = [[NSDate date] timeIntervalSince1970];
[KR_RECHARGELOG appendingString:@"-> SKProductReqFinish"];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
[KR_PROGRESS toast:STRING_LOCALIZE(@"recharge_pay_failed")];
[KR_RECHARGELOG appendingString:@"-> SKProductReqFail"];
}
/**
收到產品返回信息
*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *productArr = response.products;
if ([productArr count] == 0)
{
return;
}
SKProduct *product = nil;
for (SKProduct *pro in productArr) {
if ([pro.productIdentifier isEqualToString:_orderModel.data[@"product_id"]]) {
product = pro;
break;
}
}
if (product)
{
//此處為創建內購訂單,加入到蘋果內購iap的交易隊列
SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
//此處是把訂單號 存到 payment的applicationUsername字段上
payment.applicationUsername = _orderModel.orderNum;
//發送內購請求
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
else
{
NSLog(@"沒有此商品");
}
}
#pragma mark - SKPaymentTransactionObserver
// 監聽購買結果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased://交易完
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed://交易失敗
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored://已經購買過該商品
[self restoreTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing://商品添加進列表
break;
case SKPaymentTransactionStateDeferred://狀態未確定
default:
break;
}
}
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
//交易失敗
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
[KR_PROGRESS toast:STRING_LOCALIZE(@"recharge_reinstate")];
// 對于已購商品,處理恢復購買的邏輯
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// 驗證購買
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
// 驗證憑據,獲取到蘋果返回的交易憑據
// appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 從沙盒中獲取到購買憑據
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
//這個recptData就是交易的憑證,你可以選擇是客戶端自己去向蘋果驗證,但更多情況是 把憑證傳給服務器,讓服務器去驗證
}
在開發測試的時候可以先客戶端驗證,先驗證能不能完整走完內購流程
//沙盒測試環境驗證
#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
//正式環境驗證
#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
// 驗證購買
- (void)verifyPurchaseWithPaymentTrasaction {
// 驗證憑據,獲取到蘋果返回的交易憑據
// appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 從沙盒中獲取到購買憑據
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
// 發送網絡POST請求,對購買憑據進行驗證
//測試驗證地址:https://sandbox.itunes.apple.com/verifyReceipt
//正式驗證地址:https://buy.itunes.apple.com/verifyReceipt
NSURL *url = [NSURL URLWithString:SANDBOX];
NSMutableURLRequest *urlRequest =
[NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
urlRequest.HTTPMethod = @"POST";
NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
urlRequest.HTTPBody = payloadData;
// 提交驗證請求,并獲得官方的驗證JSON結果 iOS9后更改了另外的一個方法
NSData *result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil];
// 官方驗證結果為空
if (result == nil) {
NSLog(@"驗證失敗");
return;
}
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil];
if (dict != nil) {
// 比對字典中以下信息基本上可以保證數據安全
// bundle_id , application_version , product_id , transaction_id
// NSLog(@"驗證成功!購買的商品是:%@", @"_productName");
NSLog(@"驗證成功%@",dict);
}
}
以上基本上就是內購的流程了,但是有可能由于個種原因,導致蘋果內購的- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
回調不及時,或者當次根本就沒回調成功,等等照成訂單丟失的現象。
下面我結合我們項目的實際情況來說一下對app內購丟單的處理,我們的app邏輯是在下單的同時,后臺會給你一個訂單號,等app支付完成后,在蘋果回調時,把憑證和訂單號一塊傳給后臺,如果后臺驗證成功,則會根據訂單號增加對應的用戶id的看點。剛上線的時候,每天都收到很多看點不到賬的問題反饋,原因是因為蘋果回調的不及時,等蘋果回調了,訂單號已經不存在了,所以造成,扣款成功,服務器卻無法知道成功狀態,導致訂單丟失。
后來我們進行了優化處理,比如在上述- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
方法中,將訂單號保存到對應的SKMutablePayment,然后等到蘋果內購回調的時候,取出訂單號,然后將交易憑證和訂單號一塊發給服務器處理。
還有就是在蘋果回調的時候,將訂單號以及憑證本地化存儲,我做項目的時候采用的是,每有一個交易就存儲到本地的plist文件數組中,在很多地方去主動檢查有沒有存儲的訂單號,如果有就依次取出去拿到服務器認證,認證完成之后在本地刪除對應的憑證及訂單號。
經過優化之后投訴量基本上在幾天一個,對內購丟單有疑問的同學可以互相交流一下