【iOS】IAP內購整個流程

CJ Feng發表於2016-04-19

最近用到IAP內建購買,閱讀官方文件,在網上找了些資料,在這裡作下整理,以便日後查詢和修改,主要流程方向確定,文件和相關轉載內容截圖不一一指出,google一堆。

1.查詢官方文件,兩張目錄截圖,對主要流程大致瞭解:

                             

官方文件:

https://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html

 

 

2.In App Purchase Programming Guide

購買程式嚮導

Adding a Store to Your Application

向你的應用程式中新增商店

This chapter provides guided steps for adding a store to your application.

這個章節介紹了向你的應用程式中新增商店的詳細步驟。

The Step-By-Step Process

When you set up the project, make sure to link toStoreKit.framework. You can then add a store by following these steps:

當安裝工程時,確保連結到StoreKit.framework.,然後就可以按照下面的步驟新增一個商店到你的應用程式。

(1)Decide what products you wish to deliver with your application.

首先確定你想把哪個產品交付到你的應用程式
There are limitations on the types of features you can offer. Store Kit does not allow your application to patch itself or download additional code. Products must either work with existing code in your application or must be implemented using data files delivered from a remote server. If you wish to add a feature that requires changes to your source code, you need to ship an updated version of your application.

在你能提供的功能的型別上有一些限制。商店包(Store Kit)不允許你的應用軟體為自己打補丁或者下載附加的原始碼。產品必須同在你的應用程式或者通過遠端伺服器下載的資料檔案來執行。如果你想為產品加上一些特色,則需要更改你的原始碼,你需要為你的應用程式傳送一個更新版本。

(2)Register product information for each product with iTunes Connect.

為每個和iTunes Connect關聯的產品註冊產品資訊。
You revisit this step every time you want to add a new product to your application’s store. Every product requires a unique product identifier string. The App Store uses this string to look up product information and to process payments. Product identifiers are specific to your iTunes Connect account and are registered with iTunes Connect in a way similar to how you registered your application.

當你每一次向你的應用程式新增一個新的產品時你要重複這一步。每一個產品需要一個唯一的產品識別符號字串。App Store用這個字串來檢視產品資訊和處理付款。產品識別符號是特定於你的iTunes Connect帳戶,並且它和iTunes Connect註冊時類似於你註冊你的應用程式。
The process to create and register product information is described in the iTunes Connect Developer Guide.

更新產品的過程需要生成並註冊產品資訊,這些資訊在iTunes Connect Developer Guide.中有描述。

(3)Determine whether payments can be processed.

確定是否可以處理付款。
A user can disable the ability to make purchases inside applications. Your application should check to see whether payments can be purchased before queuing new payment requests. Your application might do this before displaying a store to the user (as shown here) or it may defer this check until the user actually attempts to purchase an item. The latter allows the user to see items that they could purchase when payments are enabled.

使用者可以禁用在程式內購買這個功能。你的應用程式應該在新的付款請求排隊之前確認付款是否可以購買。你的應用程式也可以在把商店顯示給使用者之前來確認是否可以購買或者在使用者實際企圖購買一個專案時確認是否可以購買,當付款被啟用時後者可以使使用者看見他們能購買的專案。

if ([[SKPaymentQueue defaultQueue] canMakePayments])

{

   ... // Display a store to the user.

       //顯示一個商店給使用者

}

else

{

   ... // Warn the user that purchases are disabled.

             //警告使用者不能交易

}

 

(4)Retrieve information about products.

檢索有關產品的資訊
Your application creates a SKProductsRequest object and initializes it with a set of product identifiers for the items you wish to sell, attaches adelegate to the request, and then starts it. The response holds the localized product information for all valid product identifiers.

你的應用程式建立了一個SKProductsRequest物件,並用你想賣的一系列專案的產品識別符號來初始化這個SKProductsRequest物件。附加一個delegate到這個request,然後開始。響應資訊中有所有的有效地產品識別符號的本地化產品資訊。

- (void) requestProductData

{

   SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: kMyFeatureIdentifier]];

   request.delegate = self;

   [request start];

}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

{

    NSArray *myProduct = response.products;

    // populate UI

    // 填充介面

    [request autorelease];

}

(5)Add a user interface that displays products to the user.

新增一個使用者介面來顯示產品給使用者

Store Kit does not provide user interface classes. The look and feel of how you offer products to your customers is up to you!

商店包(Store Kit)不提供使用者介面類。你提供的產品的介面風格由你來定製。

(6)Register a transaction observer with the payment queue.

用付費佇列來註冊一個交易觀察者。
Your application should instantiate a transaction observer and add it as an observer of the payment queue.

你的應用程式應例項化一個交易觀察者,並把它以付費佇列的觀察者的身份新增上去。

MyStoreObserver *observer = [[MyStoreObserver alloc] init];

[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];

 

Your application should add the observer when your application launches. The App Store remembers queued transactions even if your application exited before completing all transactions. Adding an observer during initialization ensures that all previously queued transactions are seen by your application.

你的應用程式在啟動時就應新增一個交易觀察者。即使你的應用程式在完成所有的交易之前就退出,App Store也能夠記住放在佇列中的交易。在初始化的過程中就新增一個交易觀察者,就能夠保證你的應用程式能看見之前沒有執行完的所有放在佇列中的交易。

(7)Implement thepaymentQueue:updatedTransactions: method on MyStoreObserver.

在MyStoreObserver上實現paymentQueue:updatedTransactions:
The observer’s paymentQueue:updatedTransactions: method is called whenever new transactions are created or updated.

觀察者的paymentQueue:updatedTransactions:方法在新的交易被建立或更新時就會被呼叫。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)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];

            default:

                break;

        }

    }

}

(8)Your observer provides the product when the user successfully purchases an item.

當使用者成功的購買了一個專案時,你的觀察者就會為你提供剛購買的產品。

- (void) completeTransaction: (SKPaymentTransaction *)transaction

{

// Your application should implement these two methods.

//    你的應用程式應該 實現這兩個方法

    [self recordTransaction: transaction];

    [self provideContent: transaction.payment.productIdentifier];

// Remove the transaction from the payment queue.

//從付費佇列中刪除交易

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}


A successful transaction includes a transactionIdentifier property and atransactionReceipt property that record the details of the processed payment. Your application is not required to do anything with this information. You may wish to record this information to establish an audit trail for the transaction. If your application uses a server to deliver content, the receipt can be sent to your server and validated by the App Store.

一個成功的交易應該包括一個transactionIdentifier屬性和transactionReceipt屬性,後者用於記錄處理過的付費的詳細細節。你的應用程式不要求對這個資訊作任何操作。你可能希望通過建立一個交易的審計跟蹤(audit trail)來記錄這個資訊。如果你的應用程式使用伺服器來傳輸內容(content),收據(receipt)就會被髮送到你的伺服器,並被App Store鑑定是否有效。
It is critical that your application take whatever steps are necessary to provide the product that the user purchased. Payment has already been collected, so the user expects to receive the new purchase. See“Feature Delivery” for suggestions on how you might implement this.

你的應用程式操作一些必要步驟,而這些步驟是可以提供使用者購買過的產品的,這是很重要的。付費已經被收集起來,因此使用者唯一期望的就是收到新的購買。你可以通過看“Feature Delivery””來了解你如何實現這些的建議。
Once you’ve delivered the product, your application must call finishTransaction: to complete the transaction. When you callfinishTransaction:, the transaction is removed from the payment queue. To ensure that products are not lost, your application should deliver the product before callingfinishTransaction:.

一旦你已經發出了產品,那你的應用程式就必須呼叫finishTransaction:來完成交易。當你呼叫了finishTransaction:,交易就從付費佇列中刪除掉。為了確保產品不會丟失,你的應用程式應在呼叫finishTransaction:之前就把產品發出去。

(9)Finish the transaction for a restored purchase.

為一個恢復的購買請求完成交易

- (void) restoreTransaction: (SKPaymentTransaction *)transaction

{

    [self recordTransaction: transaction];

    [self provideContent: transaction.originalTransaction.payment.productIdentifier];

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}


This routine is similar to that for a purchased item. A restored purchase provides a new transaction, including a different transaction identifier and receipt. You can save this information separately as part of any audit trail if you desire. However, when it comes time to complete the transaction, you’ll want to recover the original transaction that holds the actual payment object and use its product identifier.

這個過程同購買一個專案類似。一個被恢復的購買提供了一個新的交易,它包括一個不同的交易識別符號和收據(receipt)。你也可以把這個資訊單獨作為跟蹤審計的一部分儲存起來。儘管如此,當交易完成時,你要恢復持有實際支付物件和使用其產品識別符號的原始的交易。

(10)Finish the transaction for a failed purchase.

完成一個購買失敗的交易。

- (void) failedTransaction: (SKPaymentTransaction *)transaction

{

    if (transaction.error.code != SKErrorPaymentCancelled)

    {

        // Optionally, display an error here.

    }

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}


Usually a transaction fails because the user decided not to purchase the item. Your application can read theerror field on a failed transaction to learn exactly why the transaction failed.
通常由於使用者決定不購買這個專案而導致一個交易失敗。你的應用程式可以通過讀取購買失敗的交易中的error欄位來了解交易失敗的原因。

The only requirement for a failed purchase is that your application remove it from the queue. If your application chooses to put up an dialog displaying the error to the user, you should avoid presenting an error when the user cancels a purchase.

對於購買失敗的唯一要求就是你的應用程式從佇列中移除它。因此如果你的程式通過一個對話方塊來向使用者顯示錯誤,那麼你應該避免當使用者取消購買時提出一個錯誤。

(11)With all the infrastructure in place, you can finish the user interface. When the user selects an item in the store, create a payment object and add it to the payment queue.

你可以用所有提供的基礎設施(infrastructure)完成使用者介面。當使用者選擇了商店中的一個專案時,就建立一個交易物件,並把它加到交易佇列中。

SKPayment *payment = [SKPayment paymentWithProductIdentifier:kMyFeatureIdentifier];

[[SKPaymentQueue defaultQueue] addPayment:payment];


If your store offers the ability to purchase more than one of a product, you can create a single payment and set the quantity property.

如果你的商店能夠讓使用者購買多個商品的話,你就可以僅僅建立一個交易然後設定數量(quantity)屬性就可以了

SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:kMyFeatureIdentifier];

payment.quantity = 3;

[[SKPaymentQueue defaultQueue] addPayment:payment];

 

Where to Go Next

The code provided in these steps is best used for the built-in product model. If your application uses a server to deliver content, you are responsible for designing and implementing the protocols used to communicate between your iPhone application and your server. Your server should also verify receipts before delivering products to your application.

在這些過程中提供的程式碼能夠很好的用於基於產品的模式。如果你的應用程式使用伺服器傳送內容,你就必須設計並實現用於在你的iPhone程式和你的伺服器之間通訊的協議。你的伺服器也同樣需要在把產品傳送到你的應用程式之前對收據進行驗證。

 

 

 

3.以下英文說明主要過程:

 

Create and Fetch a Product Description

 

 

  1. Create a unique App ID
  2. Generate and install a new provisioning profile
  3. Update the bundle ID and code signing profile in Xcode
  4. If you haven’t already, submit your application metadata in iTunes Connect
  5. If you haven’t already, submit your application binary in iTunes Connect
  6. Add a new product for in-app purchase
  7. Write code for fetching the product description  //待完
  8. Wait a few hours

 

 

 

4.IAP(In App Purchase)

IAP的全稱是In App Purchase,應用內付費。這種業務模式允許使用者免費下載試用,對應用內提供的商品選擇消費,比如購買遊戲道具,購買遊戲等級等等。相比完全收費的應用而言,應用內付費給使用者試用的機會,不會讓優秀的應用因為缺乏使用者的認知而喪失消費者;而且對於開發商,也不需要為了讓使用者試用而單獨釋出一款免費的精簡版本。

商品(profuct):

 

 

使用未越獄的裝置選擇:

測試IAP的專案不能使用越獄的裝置,否則會出現無法連線到應用商店的錯誤。恢復裝置到未越獄的系統後,登陸Provisioning Portal新增裝置的UID。

使用沒有萬用字元的App ID

在定義App的Bundle ID的時候,我曾經介紹過可以使用類似 com.jamesli.* 這樣的值來覆蓋多個應用的ID。這種定義方式不能用在打算使用IAP的應用上面,定義IAP的應用必須使用唯一的ID,如com.jamesli.ghostbride。如果正確定義了,應用的In-App Purchase的功能是預設開啟的,如下圖:

 

 

 

 

 

 

iTunesConnect建立應用:

登陸iTunesConnect,建立一個新的應用,即使該應用尚未開發,也可以用一些假的文字和圖片來代替,建立好之後切記要點選Ready to Upload binary將應用的狀態變為Waiting for upload。

 

管理In-App Purchase商品

在應用列表中點選新建立的應用圖示,進入應用首頁,在右面的一行按鈕中選擇Manage In-App Purchase,進入內付費商品管理頁面。通過點選左上角的Create New按鈕可以進入商品頁面選擇建立一個新的商品。頁面中顯示的四種商品分別是我在本文介紹過的四種商品,消耗型商品(Consumable),非消耗型商品(Non-Consumable),自動重置型訂閱(Auto-Renewable Subscriptions),非自動重置型訂閱(Non-Renewing Subscription)。

 

以消耗型商品為例,點選Select進入建立頁面。Reference Name是商品名字,這不是終端使用者會看到的名字,而是會在內付費管理的商品列表中顯示的字元,類似於變數名。

Product ID是商品的唯一標識,這個ID十分重要,在編寫應用程式的時候會用它來識別改商品。

接下來是為不同的語言定義該商品的顯示名稱,終端使用者看到的就是這個名稱。定義好名稱後是為商品定價以及上傳縮圖,這個商品就算是定義完了。如下圖,定義完成的商品會顯示在內付費管理的商品列表中。每一個內付費商品的建立和修改都需要提交稽核,但這裡需要注意的是,在一個新的應用版本內建立的內付費商品,必須和這個應用版本一起提交稽核,而在該應用版本通過稽核之後再為它建立的內付費商品,可以通過這個列表中的Ready to submit按鈕來提交。

 

 

 

剛剛建立好的內付費應用,已經可以用來除錯了。

 

使用測試帳號除錯應用

蘋果應用商店是一個交易環境,任何使用者可以在這個環境內購買應用,但如果要測試正在開發過程中的應用內付費,我們不能在真正的蘋果商店裡進行。蘋果給開發者提供了一個用於除錯購買行為的測試沙箱,它完全複製了應用商店的交易環境,但在沙箱環境中我們不能用平常的蘋果帳號,而是需要用測試帳號。(只要有一個app id,就可以新增其商品,並且進行測試。)

在iTunesConnect的首頁可以點選Manage Users進入使用者管理頁面,然後選擇Test User來建立測試帳號。根據蘋果開發者的最新謝意,建立測試帳號必須使用一個真實的Email地址,而且密碼必須是符合規範的,測試賬號需要在郵件裡啟用後才可以使用。這裡建立的帳號可以用來購買開發過程中的應用內付費,但必須記住,測試帳號不能用來登陸真正的應用商店並在產品環境中進行購買行為,否則你的iTunes帳號將有可能被停用。(測試賬號可以通過itnunes connect來新增,賬號資訊隨便添就行。這裡要注意,這個賬號只能用於我們應用的沙盒測試,不要用於正常商品的購買(比如買個已上架的應用裡面的商品),否則蘋果會禁用這個賬號。)

當我們確認購買一個商品,我們會獲取一個SKPaymentTransaction物件,裡面的transactionReceipt是驗證資訊(就是一組json字串),我們對其進行base64加密,然後按照蘋果規定的格式(具體可以參考文件)傳送到驗證地址就可以了。驗證成功後,app store返回的資訊裡面包含購買商品的具體資訊,可以用於對賬。

 

購買商品後,我們本地的交易佇列中會有一個新的物件,這個交易佇列是儲存在本地硬碟上的,除非我們呼叫finishTransaction,否則交易物件不會刪除。而程式開啟時(這裡要注意一下,下面會針對這個做詳細說明)如果交易佇列不為空,則iOS會通知我們交易佇列狀態更新,我們就要根據交易物件的狀態進行處理。

SKPaymentTransactionStatePurchased  交易成功,這時已經扣完錢,我們要保證將商品傳送給使用者

SKPaymentTransactionStateFailed 交易失敗,原因很多(可以通過SKPaymentTransaction.error.code來檢視具體失敗原因),最常見的是SKErrorPaymentCancelled(使用者取消交易),或是未輸入合法的itunes id

SKPaymentTransactionStateRestored  非消耗性商品已經購買過,這時我們要按交易成功來處理。

 

如果交易失敗,我們可以直接將交易從交易佇列中移除。如果成功,則要發起驗證,等待驗證結果來進行處理。其結果無非三種,驗證成功、驗證非法、驗證錯誤。成功和非法我們都要講交易物件從交易佇列中移除,驗證錯誤則可能是驗證伺服器出現故障,我們不應該刪除該交易物件,待程式重新開啟後,再一次進行驗證,直到成功或者失敗。

 

============================================================================================

============================================================================================

 

 


In App Purchase為建立產品提供了一種通用的機制,如何操作將由你負責。當你設計程式的時候,有以下幾點需要注意:


1. 你必須提供電子類產品和服務。不要使用In App Purchase 去出售實物和實際服務。
2. 不能提供代表中介貨幣的物品,因為讓使用者知曉他們購買的商品和服務是很重要的。

 

Store Kit Guide(In App Purchase)翻譯   已完結 - Alex - 夢想千里遠,分秒追逐近

 

2. 伺服器型別
使用這終方式,要提供另外的伺服器將產品傳送給程式。 伺服器交付適用於訂閱、內容類商品和服務,因為商品可以作為資料傳送,而不需改動程式束。 例如,一個遊戲提供的新的內容(關卡等)。 Store Kit不會對伺服器端的設計和互動做出定義,這方面工作需要你來完成。 而且,Store Kit不提供驗證使用者身份的機制,你需要來設計。 如果你的程式需要以上功能,例如,紀錄特定使用者的訂閱計劃, 你需要自己來設計和實現。

圖1-3 展示了伺服器型別的購買過程。


Store Kit Guide(In App Purchase)翻譯   已完結 - Alex - 夢想千里遠,分秒追逐近 
1. 程式向伺服器傳送請求,獲得一份產品列表。
2. 伺服器返回包含產品識別符號的列表。
3. 程式向App Store傳送請求,得到產品的資訊。
4. App Store返回產品資訊。
5. 程式把返回的產品資訊顯示給使用者(App的store介面)
6. 使用者選擇某個產品
7. 程式向App Store傳送支付請求
8. App Store處理支付請求並返回交易完成資訊。
9. 程式從資訊中獲得資料,併傳送至伺服器。
10. 伺服器紀錄資料,並進行審(我們的)查。
11. 伺服器將資料發給App Store來驗證該交易的有效性。
12. App Store對收到的資料進行解析,返回該資料和說明其是否有效的標識。
13. 伺服器讀取返回的資料,確定使用者購買的內容。
14. 伺服器將購買的內容傳遞給程式。

Apple建議在伺服器端儲存產品標識,而不要將其儲存在plist中。 這樣就可以在不升級程式的前提下新增新的產品。

在伺服器模式下, 你的程式將獲得交易(transaction)相關的資訊,並將它傳送給伺服器。伺服器可以驗證收到的資料,並將其解碼以確定需要交付的內容。 這個流程將在“驗證store收據”一節討論。

對於伺服器模式,我們有安全性和可靠性方面的顧慮。 你應該測試整個環境來避免威脅。《Secure Coding Guide》文件中有相關的提示說明。

雖然非消耗性商品可以用內建模式來恢復,訂閱類商品必須通過伺服器來恢復。你要負責紀錄訂閱資訊、恢復資料。 
消耗類商品也可以通過伺服器方式來紀錄。例如,由伺服器提供的一項服務, 你可能需要使用者在多個裝置上重新獲得結果。

(這段翻譯的比較生硬,因為我個人也沒有機會把各種型別的服務跑一遍,後續會檢查並修改。希望大家一起來看看,歡迎補充。)

 

取得產品資訊

要在程式內部顯示“商店”,需要從App Store得到資訊來購建介面。 本章詳細講解如何從App Store獲取產品資訊。

向App Store傳送請求

Store Kit提供了從App Store上請求資料的通用機制。 程式可以建立並初始化一個request物件, 為其附加delegate, 然後啟動請求過程。請求將被髮送到App Store,在那裡被處理。 處理完成時, request物件的delegate方法將被非同步呼叫,以獲得請求的結果。 圖2-1顯示了請求的資料模型。

Store Kit Guide(In App Purchase)翻譯   已完結 - Alex - 夢想千里遠,分秒追逐近 
如果程式在請求期間退出,則需要重新傳送請求。

下面講解請求過程中用到的類:

SKRequest
SKRequest為request的抽象根類。

SKRequestDelegate
SKRequestDelegate是一個protocol, 實現用以處理請求結果的方法,比如請求成功,或請求失敗。

傳送獲得產品資訊的請求
程式使用products request來獲得產品的資訊。 要完成這一過程,程式需建立一個request物件,其中會包含一個產品標識的列表。之前提到過,你的程式既可以內建產品列表,又可以通過外部伺服器來獲得。

當傳送請求時,產品標識會傳送到App Store,App Store將會返回本地化資訊(這些資訊事先已經在iTunes Connect中設定好了),你將使用這些資訊來購建內建商店的介面(顯示商品名,描述,等等)。 圖2-2顯示了請求的過程。

Store Kit Guide(In App Purchase)翻譯   已完結 - Alex - 夢想千里遠,分秒追逐近 
SKProductsRequest
用來請求商品的資訊。 建立時,我們將需要顯示的商品列表加入該物件。

SKProductsRequestDelegate
該protocol定義了處理App Store響應的方法。 

SKProductsResponse
SKProductsResponse物件為App Store返回的響應資訊。裡面包含兩個列表(當然是NSArray了):一是經過驗證有效的商品,
@property(nonatomic, readonly) NSArray *products
另外一個是無法被識別的商品資訊:
@property(nonatomic, readonly) NSArray * invalidProductIdentifiers
有幾種原因將造成商品標識無法被識別,如拼寫錯誤(當然),被標記為不可出售(unavailable for sale),或是對商品資訊的改變沒有傳送到所有App Store的伺服器。(這個原因不是很清楚,再議)。

SKProduct
SKProduct物件包含了在App Store上註冊的商品的本地化資訊。

 

購買商品
當使用者準備購買商品時,程式向App Store請求支付資訊,然後App Store將會建立持久化的交易資訊,並繼續處理支付流程,即使使用者重啟程式,這個過程亦是如此。App Store同步待定交易的列表到程式中,並在交易狀態發生改變時向程式傳送更新的資料。

收集支付資訊

要收集支付資訊, 你的程式可以建立一個payment的物件,將它放到支付佇列中,如圖3-1所示。

Store Kit Guide(In App Purchase)翻譯   已完結 - Alex - 夢想千里遠,分秒追逐近 
1. 一個SKPayment的物件,包含了"Sword"的商品標識,並且制定購買數量為1。
2. 使用addPayment:方法將SKPayment的物件新增到SKPaymentQueue裡。
3. SKPaymentmentQueue包含的所有請求商品,
4. 使用SKPaymentTransactionObserver的paymentQueue: updatedTransactions: 方法來檢測所有完成的購買,併傳送購買的商品。
5. 最後,使用finishTransaction:方法完成交易。

當payment的物件被新增到支付佇列中的時候, 會建立一個持久儲存的transaction物件來存放它。 當支付被處理後,transaction被更新。 程式中將實現一個觀察者(observer)物件來獲取transaction更新的訊息。 觀察者應該為使用者提供購買的商品,然後將transaction從佇列中移除。

下面介紹在購買過程中用到的幾個類:
SKPayment
要收集支付資訊,先要了解一下支付物件。 支付物件包含了商品的標識(identifier)和要購買商品的數量(quantity)(數量可選)。你可以把同一個支付物件重複放入支付佇列,,每一次這樣的動作都相當於一次獨立的支付請求。

使用者可以在Settings程式中禁用購買的功能。 因此在請求支付之前,程式應該首先檢查支付是否可以被處理。 呼叫SKPaymentQueue的canMakePayments方法來檢查。

SKPaymentQueue
支付佇列用以和App Store之間進行通訊。 當新的支付物件被新增到佇列中的時候, Store Kit向App Store傳送請求。 Store Kit將會彈出對話方塊詢問使用者是否確定購買。 完成的交易將會返回給程式的observer物件。

SKPaymentTransaction
transaction物件在每次新增新的payment到佇列中的時候被建立。 transaction物件包含了一些屬性,可以讓程式確定當前的交易狀態。

程式可以從支付佇列那裡得到一份稽核中的交易列表,但更常用的做法還是等待支付佇列告知交易狀態的更新。

SKPaymentTransactionObserver
在程式中實現SKPaymentTransactionObserver的協議,然後把它作為SKPaymentQueue物件的觀察者。該觀察者的主要職責是:檢查完成的交易,交付購買的內容,和把完成後的交易物件從佇列中移除。

在程式一啟動,就應該為支付佇列指定對應的觀察者物件,而不是等到使用者想要購買商品的時候。 Transaction物件在程式退出時不會丟失。程式重啟時, Store Kit繼續執行未完成的交易。 在程式初始化的時候新增觀察者物件,可以保證所有的交易都被程式接收(也就時說,如果有未完成的transaction,如果程式重啟,就重新開始了,如果稍候再新增觀察者,就可能會漏掉部分交易的資訊)。


恢復交易資訊(Transactions)
當transaction被處理並從佇列移除之後,正常情況下,程式就再也看不到它們了。 如果你的程式提供的是非消耗性的或是訂閱類的商品,就必須提供restore的功能,使使用者可以在其他裝置上重新儲存購買資訊。


Store Kit提供內建的功能來重新儲存非消耗商品的交易資訊。 呼叫SKPaymentQueue的restoreCompletedTransactions的方法來重新儲存。對於那些之前已經完成交易的非消耗性商品,Apple Store生成新的,用於恢復的交易資訊。 它包含了原始的交易資訊。你的程式可以拿到這個資訊,然後繼續為購買的功能解鎖。 當之前所有的交易都被恢復時, 就會呼叫觀察者物件的paymentQueueRestoreCompletedTransactionsFinished方法。

如果使用者試圖購買已經買過的非消耗性商品,程式會收到一個常規的交易資訊,而不是恢復的交易資訊。但是使用者不會被再次收費。程式 應把這類交易和原始的交易同等對待。

訂閱類服務和消耗類商品不會被Store Kit自動恢復。 要恢復這些商品,你必須在使用者購買這些商品時,在你自己的伺服器上記錄這些交易資訊, 並且為使用者的裝置提供恢復交易資訊的機制。

 

在程式中新增Store功能
本章為新增購買功能的指導

詳細流程:

準備工作當然是新增StoreKit.framework了。
然後是具體的步驟:

1. 決定在程式內出售的商品的型別。
之前提到過,程式內可以出售的新feature型別是有限制的。 Store Kit不允許我們下載新的程式碼。 你的商品要麼可以通過當前的程式碼工作(bundle型別),要麼可以通過伺服器下載(當然,這裡下載的為資料檔案,程式碼是不可以的)。 如果要修改原始碼,就只能老實的升級了。

2. 通過iTunes Connect註冊商品
每次新增新商品的時候都需要執行這一步驟。 每個商品都需要一個唯一的商品標識。 App Store通過這個標識來查詢商品資訊並處理支付流程。 註冊商品標識的方法和註冊程式的方法類似。

要了解如何建立和註冊商品資訊,請參考“iTunes Connect Developer Guide”文件。

3. 檢測是否可以進行支付
使用者可以禁用在程式內部支付的功能。在傳送支付請求之前,程式應該檢查該功能是否被開啟。程式可在顯示商店介面之前就檢查該設定(沒啟用就不顯示商店介面了),也可以在使用者傳送支付請求前再檢查,這樣使用者就可以看到可購買的商品列表了。

例子:
if([SKPaymentQueue canMakePayments])
{
    ...//Display a store to the user
}
else
{
    ...//Warn the user that purchases are disabled.
}

4. 獲得商品的資訊
程式建立SKProductsRequest物件,用想要出售的商品的標識來初始化, 然後附加上對應的委託物件。 該請求的響應包含了可用商品的本地化資訊。

//這裡傳送請求
- (void)requestProductData
{
    SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:
    [NSSet setWithObject: kMyFeatureIdentifier]];
    
    request.delegate = self;
    [request start];
}

//這個是響應的delegate方法
- (void)productsRequest: (SKProductsRequest *)request
didReceiveResponse: (SKProductsResponse *)response
{
    NSArray *myProduct = response.products;

    //生成商店的UI
    [request autorelease];
}

5. 新增一個展示商品的介面
Store Kit不提供介面的類。 這個介面需要我們自己來設計並實現。

6. 為支付佇列(payment queue)註冊一個觀察者物件
你的程式需要初始化一個transaction observer物件並把它指定為payment queue的觀察者。

上程式碼:

MyStoreObserver *observer = [[MyStoreObserver alloc]init];
[[SKPaymentQueue defaultQueue]addTransactionObserver: observer];

應該在程式啟動的時候就新增好觀察者,原因前面說過,重啟後程式會繼續上次未完的交易,這時就新增觀察者物件就不會漏掉之前的交易資訊。

7. 在MyStoreObserver類中執行paymentQueue: updatedTransactions: 方法。
這個方法會在有新的交易被建立,或者交易被更新的時候被呼叫。

- (void)paymentQueue: (SKPaymentQueue *)queue updatedTransactions: (NSArray *)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];
            default:
                break;
        }
    }
}

上面的函式針對不同的交易返回狀態,呼叫對應的處理函式。

8. 觀察者物件在使用者成功購買一件商品時,提供相應的內容,以下是在交易成功後呼叫的方法
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
    //你的程式需要實現這兩個方法
    [self recordTransaction: transaction];
    [self provideContent: transaction.payment.productIdentifier];
    
    //將完成後的交易資訊移出佇列
    [[SKPaymentQueue defaultQueue]finishTransaction: transaction];
}

交易成功的資訊包含transactionIdentifier和transactionReceipt的屬性。其中,transactionReceipt記錄了支付的詳細資訊,這個資訊可以幫助你跟蹤、審(我們的)查交易,如果你的程式是用伺服器來交付內容,transactionReceipt可以被傳送到伺服器,然後通過App Store驗證交易。(之前提到的server模式,可以參考以前的圖)

9. 如果交易是恢復過來的(restore),我們用這個方法來處理:
- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
    [self recordTransaction: transaction];
    [self provideContent: transaction.payment.productIdentifier];

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
這個過程完成購買的過程類似。 恢復的購買內容提供一個新的交易資訊,這個資訊包含了新的transaction的標識和receipt資料。 如果需要的話,你可以把這些資訊單獨儲存下來,供追溯審(我們的)查之用。但更多的情況下,在交易完成時,你可能需要覆蓋原始的transaction資料,並使用其中的商品標識。

10. 交易過程失敗的話,我們呼叫如下的方法:
- (void)failedTransaction: (SKPaymentTransaction *)transaction
{
    if(transaction.error.code != SKErrorPaymentCancelled)
    {
        //在這類顯示除使用者取消之外的錯誤資訊
    }

    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

通常情況下,交易失敗的原因是取消購買商品的流程。 程式可以從error中讀出交易失敗的詳細資訊。

顯示錯誤資訊不是必須的,但在上面的處理方法中,需要將失敗的交易從支付佇列中移除。 一般來說,我們用一個對話方塊來顯示錯誤資訊,這時就應避免將使用者取消購買這個error顯示出來。

11. 組織好程式內“商店”的UI。當使用者選擇一件商品時, 建立一個支付物件,並放到佇列中。
SKPayment *payment = [SKPayment paymentWithProductIdentifier: kMyFeatureIdentifier];
[[SKPaymentQueue defaultQueue] addPayment: payment];

如果你的商店支援選擇同一件商品的數量,你可以設定支付物件的quantity屬性
SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier: kMyFeatureIdentifier];
payment.quantity = 3;
[[SKPaymentQueue defaultQueue] addPayment: payment];


下一步:
本章中所示程式碼可用於內建型商品模式(Built-in)。 如果你的程式要使用伺服器來發布商品,你需要負責設計和執行iPhone程式和你的伺服器之間的通訊。伺服器應該驗證資料併為程式提供內容。

驗證store的收據

使用伺服器來交付內容,我們還需要做些額外的工作來驗證從Store Kit傳送的收據資訊。

重要資訊:來自Store的收據資訊的格式是專用的。 你的程式不應直接解析這類資料。可使用如下的機制來取出其中的資訊。

驗證App Store返回的收據資訊
當交易完成時,Store Kit告知payment observer這個訊息,並返回完成的transaction。 SKPaymentTransaction的transactionReceipt屬性就包含了一個經過簽名的收據資訊,其中記錄了交易的關鍵資訊。你的伺服器要負責提交收據資訊來確定其有效性,並保證它未經過篡改。 這個過程中,資訊被以JSON資料格式傳送給App Store,App Store也以JSON的格式返回資料。
(大家可以先了解一下JSON的格式)

驗證收據的過程:

1. 從transaction的transactionReceipt屬性中得到收據的資料,並以base64方式編碼。
2. 建立JSON物件,字典格式,單鍵值對,鍵名為"receipt-data", 值為上一步編碼後的資料。效果為:
{
    "receipt-data"    : "(編碼後的資料)"
}

3. 傳送HTTP POST的請求,將資料傳送到App Store,其地址為:
https://buy.itunes.apple.com/verfyReceipt

4. App Store的返回值也是一個JSON格式的物件,包含兩個鍵值對, status和receipt:
{
    "status"    : 0,
    "receipt"    : { … }
}

如果status的值為0, 就說明該receipt為有效的。 否則就是無效的。

App Store的收據
傳送給App Store的收據資料是通過對transaction中對應的資訊編碼而建立的。 當App Store驗證收據時, 將從其中解碼出資料,並以"receipt"的鍵返回。 返回的響應資訊是JSON格式,被包含在SKPaymentTransaction的物件中(transactionReceipt屬性)。Server可通過這些值來了解交易的詳細資訊。 Apple建議只傳送receipt資料到伺服器並使用receipt資料驗證和獲得交易詳情。 因為App Store可驗證收據資訊,返回資訊,保證資訊不被篡改,這種方式比同時提交receipt和transaction的資料要安全。(這段得再看看)

表5-1為交易資訊的所有鍵,很多的鍵都對應SKPaymentTransaction的屬性。
備註:一些鍵取決於你的程式是連結到App Store還是測試用的Sandbox環境。更多關於sandbox的資訊,請檢視"Testing a Store"一章。

Table 5-1 購買資訊的鍵:

鍵名        描述
quantity     購買商品的數量。對應SKPayment物件中的quantity屬性
product_id    商品的標識,對應SKPayment物件的productIdentifier屬性。
transaction_id        交易的標識,對應SKPaymentTransaction的transactionIdentifier屬性
purchase_date    交易的日期,對應SKPaymentTransaction的transactionDate屬性
original_-transaction_id    對於恢復的transaction物件,該鍵對應了原始的transaction標識
original_purchase_-date    對於恢復的transaction物件,該鍵對應了原始的交易日期
app_item_id    App Store用來標識程式的字串。一個伺服器可能需要支援多個server的支付功能,可以用這個標識來區分程式。連結sandbox用來測試的程式的不到這個值,因此該鍵不存在。
version_external_-identifier    用來標識程式修訂數。該鍵在sandbox環境下不存在
bid    iPhone程式的bundle標識
bvrs    iPhone程式的版本號

測試Store功能
開發過程中,我們需要測試支付功能以保證其工作正常。然而,我們不希望在測試時對使用者收費。 Apple提供了sandbox的環境供我們測試。

備註:Store Kit在模擬器上無法執行。 當在模擬器上執行Store Kit的時候,訪問payment queue的動作會打出一條警告的log。測試store功能必須在真機上進行。

Sandbox環境
使用Sandbox環境的話,Store Kit並沒有連結到真實的App Store,而是連結到專門的Sandbox環境。 SandBox的內容和App Store一致,只是它不執行真實的支付動作。 它會返回交易成功的資訊。 Sandbox使用專門的iTunes Connect測試 賬戶。不能使用正式的iTunes Connect賬戶來測試。

要測試程式,需要建立一個專門的測試賬戶。你至少需要為程式的每個區域建立至少一個測試賬戶。詳細資訊,請檢視iTunes Connect Developer Guide文件。

在Sandbox環境中測試
步驟:
1. 在測試的iPhone上退出iTunes賬戶
Settings中可能會記錄之前登入的賬戶,進入並退出。

重要資訊:不能在Settings 程式中通過測試賬戶登入。

2. 執行程式
當你在程式的store中購買商品後,Store kit提示你去驗證交易。用測試賬戶登入,並批准支付。 這樣虛擬的交易就完成了。

在Sandbox中驗證收據
驗證的URL不同了:
NSURL *sandboxStoreURL = [[NSURL alloc]initWithString: 
@"https://sandbox.itunes.apple.com/verifyReceipt"];

 

原文:http://blog.csdn.net/mac_cm/article/details/8005111

相關文章