in-app purchase

增加營收獲利!為你的 App 加入內購買功能 (In-App Purchase)

增加營收獲利!為你的 App 加入內購買功能 (In-App Purchase)
增加營收獲利!為你的 App 加入內購買功能 (In-App Purchase)
In: in-app purchase, Swift 程式語言

不可否認的是,最近這幾年,大部分開發者的收入都是仰賴 App 內購買。如果仔細想的話,答案更是不言自明。對於開發者而言, IAP 是一套很棒的工具,而對於使用者來說,則提供了很棒的選擇。有了 IAP ,兩造雙方都非常開心;開發者可以在 App Store 上面販售功能有限的免費 App ,以便吸引更多的使用者來嘗鮮,並且整合 IAP 以便在使用者購買後可以解鎖使用額外的內容。另一方面,使用者可以只購買他們感興趣的內容或功能,對於不想使用的部分則完全無須付費。經過證實,這項策略確實讓幾乎所有使用 IAP 的開發者都賺到了錢。

in-app-purchase-featured

在本文中,一如預期,我打算示範如何在 App 中整合 IAP 。你將會看到這一點也不困難,而且針對每支 App 你需要採取的步驟幾乎都是一樣的。簡單地說,下列是在 iOS App 當中整合 IAP 所需要的步驟:

  1. 在 Xcode 中進行一些基本的專案設定。
  2. 在 iTunes Connect 入口中建立 IAP 產品項目。
  3. 新增特定的程式碼,以便從 Apple 取得 IAP 產品資訊,並且完成結帳程序。

當然了,我們會在稍後的幾個小節中看到上述這些步驟的細節。不過在此之前,請容我先強調幾個你應該知道的事實和概念。在上述的清單中,我使用了「產品」( Product )這個字眼,這不是隨意挑選的。這個字彙也是 Apple 在處理 IAP 時的用法,此外也代表著你在 App 中所銷售的東西。每件 IAP 產品皆屬於下列幾種類型( Category )之一,端視 App 本身以及銷售類型而定:

  • 消耗性項目( Consumable ):這類產品可以重複購買,不限只有一次。舉例而言,在遊戲中,消耗性產品可能是主角的生命(你可以幫主角買好多條命)。消耗性產品只會將 App 功能解鎖一小段時間,並非永久有效。
  • 非消耗性項目( Non-Consumable ):這類產品是前一類產品的相反,就跟你理解的一樣,非消耗性產品只會購買一次。舉例而言,在文字編輯 App 中購買額外的字型只需交易一次,即可永遠使用那些字型。
  • 自動續訂型訂閱( Renewable Subscriptions ):從名稱即可猜測出來,這類產品可以隨著時間一再續訂(時間週期可以在 iTunes Connect 中設定),適用於銷售需要訂閱的服務。
  • 非續訂型訂閱( Non-Renewable Subscriptions ):這類產品是前一類產品的相反,非續訂型訂閱只會持續一段時間然後就會過期。非常適合用於提供不需要再次續訂、一段時間之後就會結束的服務。

除了上述這些類型之外,也請牢記一點,就是應該在開發階段進行 IAP 的測試。換言之,在打造 App 期間,永遠不應該使用真實的 Apple ID ,也永遠不要真的付錢購買某項服務或產品。值得慶幸的是,你可以在整個開發階段,透過在 iTunes Connect 平台中建立並使用測試人員( Test Users )以便在沙箱( Sandboxed )模式中工作。透過這種方式,你可以針對自己的 IAP 進行大量的測試,而不需要付出半毛錢。

最後,準備好實機以便測試本文的範例 App (以及任何你已經整合好 IAP 功能的 App )。你無法在模擬器上購買產品,即使有辦法取得 IAP 產品明細並且顯示在 App 上。

在閱讀完本文之後,如需更多的協助與資源, Apple 的官方文件非常值得一讀。由於篇幅有限,本文無法涵蓋所有的細節(在本文的最後,我會摘要出我們討論過的議題),所以請不要忽略原始的官方文件。

範例 App 簡介

現在讓我們來看一下本文的範例 App 吧。首先,你應該從下載 Starter 專案開始,以此做為實作的進入點。此專案是一個非常簡單的繪圖 App ,手指在螢幕上滑來滑去即可隨意畫圖。此 App 總共提供 6 種顏色:紅、黃、綠、藍、黑、灰。不過只有前 2 種顏色可以免費使用。其他 4 種顏色必須透過 IAP 取得。

更精確地說,其他 4 種顏色將會分成 2 款顏色組合,成為我們要在 App 中「販售」的產品:一款包含綠色和藍色,另一款則包含黑色和灰色。你可以在 App Store 找到類似的 App ,不過我們的版本相對簡單許多。

t37_1_drawing_sample

根據我在引言中的介紹,你可以預期我們的 IAP 產品(顏色組合)屬於非消耗性類型。沒錯,但是在此我們不打算使用此類產品。基於本文的教學目的,以及為了能夠重複測試 IAP 流程,我們所建立的顏色組合將會歸類為消耗性產品。這意味著每次當此 App 啟動時,都必須再次購買那些額外的顏色才能夠再次使用,儘管在上次執行此 App 時已經買過了。但是請記住,這麼做只是為了方便示範。假使你打算建構類似的 App ,那麼你的產品絕對應該是非消耗性的,也就是說只需要購買一次而已。

在 App 中使用 IAP 時,開發者的任務之一,就是建立一個用來進行購物交易的特殊視圖控制器。此視圖控制器並沒有預設的格式,不過顯然最好看起來跟 App 的其他部分一致。除此之外,此視圖控制器可以包含任何能夠清楚示意並簡化購物流程的元件。因此你可以在 Starter 專案中找到一個名為 IAPurchaseViewController 的視圖控制器。我們在裡面加入了啟用購物並解鎖範例 App 額外功能所需的程式碼。

除此之外,你也可以找到另一個名為 CanvasView 的類別。它繼承自 UIView ,並且包含了範例 App 繪圖功能所需的程式碼。

就跟所有的 Starter 專案一樣,你會看到部分程式碼已經寫好了。我建議你可以先看過一遍,而且也瀏覽一下 Storyboard 中現有的場景。這麼做可以幫助你在開始處理 IAP 之前先熟悉一番。

在本文接下來的幾個小節中,我們將會看到要讓 IAP 生效所需的主要及必要的步驟。這意味著我們將從設定 Xcode 開始,然後造訪 iTunes Connect 入口以便建立要在 App 中販售的產品,最後再將所需的程式碼補齊。有個很重要的事實必須強調一下,如果你沒有開發者帳號,將無法繼續完成本文的其餘步驟,因為進入 iTunes Connect 入口和啟用 IAP 都需要使用到開發者帳號。

設定 Xcode 專案

在實際撰寫程式碼之前,整合了 IAP 的 App 都需要在 Xcode 中完成幾項設定。我們必須確定專案中已加入了所需的框架, Xcode 會幫我們做完必要的設定。如你所見,事情就是這麼簡單,我們只需要做 2 件事:

  1. 在專案中啟用 IAP 。
  2. 設定有效的 iOS 開發者帳號(以 Xcode 的術語來說叫做「團隊」〔 Team 〕)。

讓我們從第 2 個步驟開始,首先在 Xcode 中開啟 Starter 專案,載入完畢之後點擊 Project Navigator 中的專案目標。接著,在 General 分頁的 Identity 區段中,點擊 Team 下拉選單並選取你的開發者帳號,如下圖所示:

t37_2_set_team

假使你尚未在 Xcode 中加入自己的開發者帳號,以致無法在上述的下拉選單中找到,那麼請遵循下列步驟:

  1. 開啟 Xcode > Preferences… 功能表。
  2. 點擊 Accounts 分頁。
  3. 點擊左下角的加號按鈕,並選取 Add Apple ID… 選項。
  4. 將會出現強制回應視窗,輸入完你的 Apple ID 和密碼之後,按下 Add 。

最後,你的開發者帳號應該會出現在如下的畫面截圖當中:

t37_3_add_apple_id

現在可以遵循上述的程序,為此專案設定有效的 Team 數值了。

接著,讓我們來為此 App 啟用 IAP 吧。同樣透過 Project Navigator 面板來選定專案目標,這回點擊的是 Capabilities 分頁。在功能清單中,找到 In-App Purchase 並切換成 ON :

啟用 IAP

在繼續往下閱讀之前,只想讓你知道,我們在本小節所做的這 2 個步驟都是必要的,以便讓我們的專案擁有新的 AppID ,並且與有效的開發者帳號相連結。接下來要做的事情,少了這 2 個步驟可就不行了。

測試人員

我們要先離開 Xcode 一下子,轉而使用瀏覽器來操作 iTunes Connect 入口網站。所以,請造訪此連結,提供你的帳號和密碼以便登入,然後繼續往下閱讀。

我們要做的第一件事,就是建立一名(如有需要的話,也可以建立多名) Test Users (測試人員)。稍後在測試 App 期間我們將會使用測試人員來確保所有的購物交易都是在沙箱環境中以虛擬貨幣進行的,這樣我們在開發期間就不需要實際支付費用了。請留意,在開發及測試期間務必使用測試人員;就算你的 App 運作良好,但是一再使用真實貨幣來做交易一點意義也沒有。所以請牢記在心:建立並使用測試人員。

現在讓我們來看一下流程。在連接到 iTunes Connect 平台之後,點擊 Users and Roles (使用者和職能)連結以便進入設定測試人員的地方。在此新頁面的最上方有 3 個連結:

  1. iTunes Connect Users ( iTunes Connect 使用者)
  2. TestFlight Beta Testers ( TestFlight Beta 版測試人員)
  3. Sandbox Testers (沙箱技術測試人員)

點擊第 3 個連結(沙箱技術測試人員)以便處理測試人員。如果你已經新增過測試人員了,那麼將可以在此看到清單。

現在讓我們來建立新的測試人員。在頁面左上角、 Testers (測試人員)標題旁邊有個加號按鈕:

Sandbox testers

點擊此按鈕,將會進入新的頁面,你可以在此輸入測試人員的所有資料。請確定全部的欄位都已填寫,這樣才能夠儲存。電子郵件可以不必是真的,但是要記住不能夠使用已經填過的電子郵件。在填寫完資料之後,儲存使用者並且返回前一個頁面。

此時,你應該可以看到出現了新的測試人員(連同既有的其他測試人員),這樣我們在這個步驟的工作就做完了。

t37_6_list_test_users

新的 App 項目

我們必須進行本小節的步驟,才能夠在範例 App 中使用 IAP 功能。或許你已經猜到了,我們將會建立一筆新的 App 項目來連結我們先前為範例 App 所建立的 AppID ,然後進行一些與 IAP 有關的設定。

在 iTunes Connect 平台中(假設你還停留在使用者頁面中),點擊左上角的 Users and Roles 連結(就在 iTunes Connect 標題旁邊),並從彈出功能表中選取 My Apps (我的 App )連結。此動作會將你帶領到 App 項目頁面,你可以在此看到所有開發過和正在 App Store 上銷售的 App 。

左上角有一個大大的加號按鈕,請點擊並選取 New iOS App (新的 iOS App )以便建立新的 App 項目。

t37_7_my_apps

在彈出視窗中,仔細填入與此新 App 有關的資訊。請為此 App 挑選不重複的獨一無二名稱,請不要嘗試使用 IAPDemo 之類的常見名稱,因為已經被用走了(請留意,下列的畫面是我在發現此名稱已經被用過之前所擷取的,所以我也需要再另外挑選別的名稱)。為了方便起見,本文在此命名為 IAPDemo-Appcoda ,但是因為名稱已經被我用走了,所以請你自行選擇喜歡的名稱。

t37_8_new_ios_app

除了名稱之外,還需要特別留意一件事:此 App 的 Bundle ID (套裝組 ID )。從下拉功能表,確定選取了與正在建立的專案相符的正確 AppID ( com.appcoda.IAPDemo )。這樣你才能夠與此新的 App 項目產生連結,並且繼續完成範例 App 的開發。

最後,設定此 App 的主要語言、版本(因為現在正在開發的是範例專案,所以版本編號在此並無實際的意義)以及 SKU 數值(不重複的獨一無二字串)。

在輸入完這些資料之後,點擊 Create 按鈕,將會導引到 App 明細頁面。裡頭提供了許多連結,你可以找到名為 In-App Purchases 的項目。點擊此連結,因為我們即將建立 2 筆新的 IAP 項目,最終也將會提供可以購買的產品。

t37_9_create_new_iap

如前所述,我們希望能夠透過此 App 來購買新的顏色組合,所以我們在此所建立的項目必須包含相關的資訊。此外我也說明過,在實際的 App 當中,這類型的產品應該要是非消耗性的,但是為了方便示範,所以在本文中我們建立成消耗性的產品。

接著按下左上角的 Create New 按鈕。首先要為我們所建立的新 IAP 選取適當的類型。為了方便示範,請點擊 Consumable (消耗性項目)區段中的 Select (選取)按鈕:

t37_10_select_iap_type

現在我們必須設定 IAP 的細節。在 Reference Name (參照名稱)欄位中,設定你想要在 iTunes Connect 入口中參照的字串(如果此 App 有在銷售的話,也會運用於 App 項目和銷售中)。在 Product ID (產品 ID )欄位中,輸入不重複的獨一無二字串,用來提示(也用來提醒你自己)目前的 IAP 項目。如下圖所示:

t37_11_iap_settings1

接著選擇 Price Tier (價格層級),並且往下捲動設定額外的 IAP 資料。另外還有一個非做不可的步驟,就是透過 Add Language (新增語言)按鈕來設定 Title (顯示名稱)和 Description (描述)的偏好語言。選好語言之後,將顯示名稱設定為 Extra Colors Collection 1 ,並將描述設定為 Extra colors for drawing (Green and Blue). 。儲存語言,然後儲存 IAP 項目。

t37_12_iap_details

重複一次上述的步驟,以便新增另一項 IAP 產品,這回設定的顯示名稱和描述分別如下:

  • Extra Colors Collection 2
  • Extra colors for drawing (Black and Gray).

最後,你應該可以看到如下圖所示的 2 筆項目:

t37_13_new_iap_record

請留意,針對真實的 App ,你應該在 Review Board 中提供圖片和說明,以便幫助審查人員了解 IAP 產品並加速 App 的審查作業。

現在我們在 iTunes Connect 平台中的工作已經做完了。請確定記下了這 2 項產品的 ID ,因為在稍後的程式碼中將會需要使用到。現在,我們已經擁有一筆與範例 App 連結的 App 項目,以及 2 項 IAP 產品,稍後我們就可以進行虛擬購物交易了。

取得產品資訊

根據我們稍早的說明,建立和顯示用來進行購物的視圖控制器是開發者的任務之一。在你剛才下載的 Starter 專案中,可以找到這樣的視圖控制器,名為 IAPurchaseViewController 。在此視圖控制器中,我們將會加入缺漏的程式碼和邏輯,以實現 IAP 產品的購買與取得。因為這是本文的核心,所以我們將逐步一一解說。

那麼,在本小節中,我們將取得先前在 iTunes Connect 入口中所建立的 2 項產品的 IAP 明細,並且在 IAPurchaseViewController 視圖控制器的表格視圖中顯示這些項目。我們已經為範例 App 建立了 2 項假定的額外顏色組合;透過購買第 1 組,將可以解鎖綠色和藍色的功能,而購買第 2 組則可以取得黑色和灰色。

現在讓我們來撰寫一些程式碼吧。首先從 Project Navigator 面板點擊以開啟 IAPurchaseViewController.swift 檔案。在檔案的開頭、 import UIKit 這行下方,加入下列這行:

import StoreKit

上述的程式庫能夠讓我們使用一些實作 IAP 所需的類別和協定。

接著,在此類別中,同時宣告和初始化下列這些陣列:

var productIDs: Array = []

var productsArray: Array = []

productIDs 陣列中,我們將新增先前於 iTunes Connect 平台中所建立的那 2 項 IAP 產品的 ID 數值。跟你理解的一樣,此陣列的內容將被 StoreKit 框架用來從 Apple 伺服器擷取我們正在查看的產品的資料。

至於 productsArray 則是在找到並取得符合的項目之後使用的。如你所見,此陣列的每個元素都必須是 SKProduct 物件,亦即以 iOS SDK 程式形式所描述的 IAP 產品的類別實體(詳情請看這裡)。

除了上述這 2 個宣告之外,接著來到類別的表頭列,加入 SKProductsRequestDelegate 協定。示範如下:

class IAPurchaceViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SKProductsRequestDelegate

在加入上述的協定之後, Xcode 將會顯示錯誤,但是請先不必擔心。那是因為尚未實作委派函式的內容,我們稍後就會補上。

現在,讓我們來為 productIDs 陣列加入一些內容。在 viewDidLoad 函式中,加入下列這 2 行:

override func viewDidLoad() {
    ...

    productIDs.append("iapdemo_extra_colors_col1")
    productIDs.append("iapdemo_extra_colors_col2")
}

請留意,上述看到的產品 ID 數值就是我們在 iTunes Connect 中所設定的 IAP 項目。假使你使用的不是這些數值,請置換成你自己設定的正確 ID ,否則在測試時 IAP 將無法運作。

下一步是提出要求並取得我們想要的產品資訊。以程式設計來說,這意味著我們將首次使用從未看過的類別,名稱是 SKProductRequest 。整個要求作業將在我們所建立的新自訂函式中進行,我們將會對該類別的物件進行初始化、帶入產品 ID ( productIDs 陣列),並且觸發要求。我們稍後會在委派函式中處理回應,不過就目前而言,先關注要求就可以了。接下來我將提供一個名為 requestProductInfo() 的新自訂函式,用來執行我剛才說明過的任務:

func requestProductInfo() {
    if SKPaymentQueue.canMakePayments() {
        let productIdentifiers = NSSet(array: productIDs)
        let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set)

        productRequest.delegate = self
        productRequest.start()
    }
    else {
        println("Cannot perform In App Purchases.")
    }
}

說得更詳細一些:如你所見,必須確保購物能夠在執行此 App 的實機上完成,因此唯有此條件成立才會繼續往下執行。上述程式碼中的 SKPaymentQueue 是隸屬於 StoreKit 框架的類別,負責在選定了要購買的產品之後完成實際的交易。我們會在下一個小節再度使用到此類別;現在我們只需要用它來確認實機的結帳功能。

接著,我們使用到 SKProductsRequest 類別,並且透過提供包含了 productIDs 內容的 NSSet 物件來初始化 productRequest 物件。儘管此類別的正確用法看起來或許並不那麼直覺,但是仍舊算是簡單。

最後,在初始化完 productRequest 物件之後,將我們的類別設定為其委派(以便處理其回應),然後觸發要求。請留意,如果實機無法執行購物交易的話,我們會在主控台上顯示一段訊息,不過當然了,在實際的 App 當中應該避免這麼做。

上述函式必須要被呼叫,所以我們在 viewDidLoad 函式中做這件事:

override func viewDidLoad() {
    ...

    requestProductInfo()
}

現在,讓我們來處理上述要求的回應(來自 Apple 伺服器)吧。先前我們已經加入過 SKProductRequestDelegate 協定了,現在開始來實作其委派函式,示範如下:

func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {

}

response 參數值包含了非常重要的屬性,名為 products 。這是一個陣列,其中的每個元素都是 SKProduct 物件,包含的是我們在 iTunes Connect 中所建立的每項 IAP 產品的資訊。我們要在上述函式中進行的工作非常簡單:一旦確認了回應確實包含產品物件,便將這些產品新增到我們先前宣告和初始化過的 productsArray 陣列中,然後重新載入表格視圖的資料(要用來顯示產品資訊的地方)。程式碼示範如下:

func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
    if response.products.count != 0 {
        for product in response.products {
            productsArray.append(product as! SKProduct)
        }

        tblProducts.reloadData()
    }
    else {
        println("There are no products.")
    }
}

上述的實作夠直覺了吧,可以說關於此函式的相關工作我們已經做完了。不過上述的實作還可以再更正確一些,如果我們一併加入下列這幾行的話:

func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
    ...

    if response.invalidProductIdentifiers.count != 0 {
        println(response.invalidProductIdentifiers.description)
    }
}

你可能已經從 invalidProductIdentifiers 屬性名稱中猜到了,有可能因為產品 ID 已經停用了而傳回空白的結果。在真實的 App 中,這種情況很可能發生,因為你隨時都可以建立和刪除 IAP 產品,導致你的 App 所要求的產品 ID 已不復存在。當然了,在此範例 App 中並不會發生這種情況,除非你手動刪除了某項 IAP 產品。毫無疑問的是,在真實的 App 當中,你應該以更優雅的方式來處理無效的產品,而不只是在主控台中列出訊息而已(畢竟這段訊息沒有人看得見)。

這是我們現階段要做的最後一件事;撰寫正確更新表格視圖的函式,好讓取得的產品資訊能夠被顯示出來。

首先,讓我們來修改一下比較簡單的那幾個函式:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}


func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return productsArray.count
}


func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 80.0
}

上述實作中唯一真正有趣的是「告訴」表格視圖顯示與 productsArray 陣列元素相同數量的產品物件資料列:

現在,讓我們來顯示每項 IAP 產品資訊吧:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("idCellProduct", forIndexPath: indexPath) as! UITableViewCell

    let product = productsArray[indexPath.row]
    cell.textLabel?.text = product.localizedTitle
    cell.detailTextLabel?.text = product.localizedDescription

    return cell
}

為了簡化程序,首先我們會將與每列相符的產品物件儲存到名為 product 的區域變數中。接著,存取其 localizedTitlelocalizedDescription 屬性,並且將這些數值分別指派給儲存格的文字標籤和明細文字標籤。這樣就完成了!

至此,你便可以測試此 App 並且嘗試首度使用 IAP 功能。因為尚未進入付款階段,所以如有需要的話,可以在模擬器中執行此 App (否則應該要直接在實機上面執行)。在載入完 IAPurchaseViewController 之後,等待幾秒鐘,讓此頁面從 Apple 伺服器取回 IAP 項目。為了簡化實作,我並未加入活動指示器(或者類似的元件),總之就是等待。假使你截至目前為止都有遵循每個小節的每個步驟,那麼執行起來應該不會碰到什麼大問題。

下圖說明的是從 Apple 擷取到的可以透過此 App 來購買的額外顏色組合:

t37_14_list_products

購買產品

在成功將 IAP 產品條列到表格視圖上之後,本小節要做的便是實作個別項目的購買動作。我們需要向使用者提供 Buy (購買)按鈕(或是類似的按鈕),以便在點擊後觸發購買流程。針對此按鈕,我們需要知道幾件與 StoreKit 類別和購買流程有關的事情。

由於本文提供的是示範用途的 App ,所以我們不會用很炫的方式來呈現 Buy 按鈕。最簡單的作法就是每次在儲存格(對應到一項 IAP 產品)被點擊之後都顯示 動作表單 ( Action Sheet ,一種通知控制器)。此動作表單顯然只需要包含 2 個選項: Buy 和 Cancel 。

讓我們繼續撰寫缺漏的程式碼,首先在 IAPurchaseViewController 類別中宣告 2 個新的屬性。來到此類別的開頭,可以看到已經宣告好的變數,接著新增下列這 2 個:

var selectedProductIndex: Int!

var transactionInProgress = false

我們將使用 selectedProductIndex 來記錄被點擊的資料列索引,以便存取 productsArray 陣列中對應的 SKProduct 物件,並且購買。而 transactionInProgress 旗標則會在交易(購物流程)開始時變成 true ,以避免在第 1 筆交易結束之前,又啟動了另一次購買流程。並沒有規定不能夠同時啟動多筆交易;你也可以不要使用此旗標,但是因為我們正在實作的是範例 App ,所以我傾向於每次只購買一項產品。

現在,讓我們來實作下列的表格視圖委派函式:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    selectedProductIndex = indexPath.row
    showActions()
    tableView.cellForRowAtIndexPath(indexPath)?.selected = false
}

首先,如前所述,我們將被點擊資料列的索引值儲存到 selectedProductIndex 屬性中。接著,我們呼叫了稍後馬上就會實作的 showActions() 自訂函式,以便顯示動作表單並且取消選取被點擊的儲存格。

showActions() 函式的定義如下:

func showActions() {
    if transactionInProgress {
        return
    }

    let actionSheetController = UIAlertController(title: "IAPDemo", message: "What do you want to do?", preferredStyle: UIAlertControllerStyle.ActionSheet)

    let buyAction = UIAlertAction(title: "Buy", style: UIAlertActionStyle.Default) { (action) -> Void in

    }

    let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { (action) -> Void in

    }

    actionSheetController.addAction(buyAction)
    actionSheetController.addAction(cancelAction)

    presentViewController(actionSheetController, animated: true, completion: nil)
}

請留意,在此函式的開頭,我們首先檢查了是否已有交易正在進行中。如果有的話,便直接返回,不再執行後續的程式碼。否則的話,後續的動作其實非常簡單,而且你一點也不陌生。目前你在執行的時候並不會發生什麼事,因為尚缺 buyAction 動作的程式碼。

在啟用購物功能之前,必須先加入 IAPurchaseViewController 類別做為交易流程的觀察者( Observer )。這麼做的話,我們不僅能夠觸發交易,還可以得知交易的進度以及是否順利完成,進而最終幫 App 解鎖這項額外功能。所以請來到此類別的表頭,並且加入下列的協定:

class IAPurchaceViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SKProductsRequestDelegate, SKPaymentTransactionObserver

Then, in the viewDidLoad method let this class observe for the transactions:

override func viewDidLoad() {
    ...

    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}

現在我們可以來到 buyAction 通知動作中進行選定產品的購買流程。在下列的程式碼片段中,我只會展示與此動作有關的部分,至於 showActions() 函式的其餘部分在稍早已經看過了。程式碼如下所示:

let buyAction = UIAlertAction(title: "Buy", style: UIAlertActionStyle.Default) { (action) -> Void in
    let payment = SKPayment(product: self.productsArray[self.selectedProductIndex] as SKProduct)
    SKPaymentQueue.defaultQueue().addPayment(payment)
    self.transactionInProgress = true
}

此處有 2 個很重要的步驟:一、使用與點擊的資料列相符的 SKProduct 物件來初始化 SKPayment 物件。現在你可以看到 selectedProductIndex 屬性的用途了。二、透過將付款物件加入付款佇列以便啟動交易。這麼做的話,交易將會開始在背景執行,而 SKPaymentTransactionObserver 則會持續透過下列的委派函式來告知我們關於不同流程階段的變化:

func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
    for transaction in transactions as! [SKPaymentTransaction] {
        switch transaction.transactionState {
        case SKPaymentTransactionState.Purchased:
            println("Transaction completed successfully.")
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            transactionInProgress = false
            delegate.didBuyColorsCollection(selectedProductIndex)


        case SKPaymentTransactionState.Failed:
            println("Transaction Failed");
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            transactionInProgress = false

        default:
            println(transaction.transactionState.rawValue)
        }
    }
}

交易流程牽涉到許多狀態,不僅只上述看到的這 2 種,你可以透過存取 SKPaymentTransactionState 列舉數值來得知。不過我們在此只對交易的最終狀態感興趣:產品是否已購買,或是交易失敗了。基於示範的目的,我們會在 default 情況中將與交易流程處理的狀態有關的原始數值列印到主控台中。

如同你在上述程式碼中所發現的,我們在 2 種情況中都執行了一些共同的動作:

  • 在主控台列印與交易結果有關的訊息(這是選用的,在真實的 App 中並不需要這麼做)。
  • 透過呼叫 finishTransaction(_:) 函式(這是非常重要而且永遠不應該被忽略的步驟)來完成交易。
  • transactionInProgress 旗標再度設為 false ,以便能夠再度啟動新的購物。

不過重點在於,針對成功的交易,我們會進行下列的呼叫:

delegate.didBuyColorsCollection(selectedProductIndex)

上述的呼叫將會引導至解鎖流程,釋放 App 中某項受限的功能。如你所知,我只是先提供尚未實作的協定委派函式,我們很快就會補齊內容。

解鎖 App 功能

在成功完成交易,而且使用者也為 App 的額外功能支付過費用之後,我們便必須啟用那些功能。在真實的 App (例如我們現在正在開發的這支範例 App )當中, IAP 通常應該是永久解除某個鎖定的功能,不過基於測試的目的,在本範例 App 中我們違反了這項常規。相反地,在 App 每次載入時,範例 App 所支援的額外顏色都是上鎖的,必須再次購買才行。

現在讓我們把重點放在如何將功能解鎖,你會看到我們將使用委派設計模式來通知 ViewController 類別已經購買了某個新的顏色組合,因此必須將這些顏色提供給使用者。我們已經呼叫過尚未存在的委派函式,所以現在就讓我們來補齊缺漏的程式碼吧。

IAPurchaseViewController 類別中,來到開頭的表頭列,並加入下列的協定:

protocol IAPurchaceViewControllerDelegate {

    func didBuyColorsCollection(collectionIndex: Int)

}

如你所見,這個新的協定只包含 1 個函式(我們已經先呼叫過了),名為 didBuyColorsCollection(_:) 。此函式只需要 1 個參數,就是我們想要啟用的顏色組合的索引。

現在來到類別本體,宣告如下的委派屬性:

var delegate: IAPurchaceViewControllerDelegate!

至此便完成了 IAPurchaseViewController 類別的準備工作!這裡沒有其他要做的事了,所以讓我們再度回到 ViewController.swift 檔案,讓 ViewController 成為 IAPurchaseViewController 的委派。

ViewController 類別中,首先來到表頭,並加入上述的協定,好讓我們能夠在稍後呼叫委派函式:

class ViewController: UIViewController, IAPurchaceViewControllerDelegate

下一步是將 ViewController 類別設定為 IAPurchaseViewController 的委派。因為在 Interface Builder 中已經在 2 個類別之間設定了一個 Segue ,所以此設定可以在 prepareForSegue(...) 函式中完成,此函式會在 Segue 執行之前先被呼叫到。此函式尚未存在於 ViewController 類別中,現在就讓我們來加入此函式吧:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "idSegueIAP" {
        let iapViewController = segue.destinationViewController as! IAPurchaceViewController
        iapViewController.delegate = self
    }
}

首先透過 Segue 物件的 destinationViewController 屬性來存取 IAPurchaseViewController 實體,接著將此類別設為其委派。一點也不難吧。

現在,我們可以實作委派函式,並且根據所購買的顏色組合來解鎖適當的顏色:

func didBuyColorsCollection(collectionIndex: Int) {
    if collectionIndex == 0 {
        btnGreen.hidden = false
        btnBlue.hidden = false
    }
    else {
        btnBlack.hidden = false
        btnGray.hidden = false
    }
}

太好了, ViewController 也已準備就緒!

現在我們要做的,就是測試 IAP ,不過別忘了需要在實機上面操作。在執行此 App 之前,來到 Settings (設定) App 的 iTunes and App Store ( iTunes 與 App Store )設定中,如果已經登入的話,請先將帳號登出。這樣你才能夠使用先前所建立的測試人員,而且當然了,完全不必支付半毛錢去購買並不存在的產品。

當你在執行此 App 時,請等候片刻,以便從 Apple 伺服器取回顏色組合,接著點擊任意項目以便購買。假設你已經從自己的帳號登出,那麼首先將會被要求登入:

t37_15_signin_alert

點擊 Use Existing Apple ID 按鈕,然後輸入測試人員的帳號密碼。登入之後,將會顯示確認通知,最後一次詢問你是否確定要購買選定的產品。你會注意到在底部有額外一行文字,指出正在沙箱模式中運作(如果沒有看見這行的話,代表你不是使用測試人員的帳號,最好不要繼續往下操作了)。

t37_16_confirm_buy

等待片刻,在交易完成時,新的通知控制器會讓你知道購物已經完成,所以你可以返回 ViewController 並且看到存在那裡的額外顏色。

請留意,完成交易通常需要花費幾秒鐘的時間,所以請不要關閉購物的視圖控制器。在真實的 App 當中,應該要讓使用者知道購物交易正在進行中,不過我們在此省略了此步驟。此外,別忘了看一下主控台(在 Xcode 裡面),你將在那裡看到輸出的訊息。如果交易失敗了,主控台那邊會有訊息指出這點。

補充說明

我們的範例 App 運作良好,在大部分情況中,你在整合 IAP 時需要做的就是這些。然而因為本文只是一個教學性質的範例,所以存在著無可避免的缺陷。你應該有發現,我們在 IAPurchaseViewController 類別中明確設定了想要透過 IAP 販售的產品 ID 數值,就位在 viewDidLoad 函式內:

productIDs.append("iapdemo_extra_colors_col1")
productIDs.append("iapdemo_extra_colors_col2")

這種作法完全沒有彈性,假使你未來想要加入新的產品或是刪除原有的產品,將會造成一些問題。假使你是採用這種作法,那麼每次在變更完 IAP 產品之後,就需要發佈新的 App 更新,這實在是非常糟糕的作法。

實際上你應該做的,是在你自己的伺服器上面存放一份列有特定時間點應該販售哪些 IAP 產品的 ID 清單,然後每次在你的 App 啟動時都去存取這份清單。這樣的話,便可以使用取得的清單來要求產品資訊並在 App 中呈現,而不是像本例中看到的明確指定方式。此外,如果是從伺服器取得清單的話,你就不必在新增或刪除 IAP 產品時還需要更新 App ,而可以保有最大的彈性。

除此之外,在銷售非消耗性項目時必須特別提醒一件事(雖然本文示範的是消耗性項目)。在成功完成 IAP 購物之後,必須向交易要求收據( Receipt ),並且在 App 和伺服器兩地留存。這麼做的話,每次當 App 啟動時,就可以檢查現有的收據,然後就知道哪些功能已經購買過了,並予以解鎖。對於非消耗性產品(使用者只會購買一次)而言此動作是必要的,但是消耗性產品則不需要(使用者必須持續購買)。

你可以在 Apple 官方文件中看到上述這些 IAP 的細節,這也是我鼓勵你閱讀官方文件的原因。

結語

你又看完一篇 iOS 教學了,本文介紹的是你在整合 IAP 時需要知道的細節。記住, IAP 是增加收益的絕佳辦法,而且整合起來一點也不困難。如果你打算在自己的 App 中使用 IAP ,那麼應該考慮我在本文中所強調的那些重點。無論如何,本文提供了很棒的學習,不過有些細節則留待你自己完成。藉由採取適當的作法,便可以將銷售提昇至最大量,同時又令使用者開心,因為他們買到了自己想要的產品。所以請自行斟酌衡量,努力往 IAP 獲利之路邁進吧!

供你參考,請從這裡下載本文的 Xcode 專案。

你對於本文有什麼心得呢?歡迎留言分享你的想法。如果你覺得本文對你有幫助的話,請不吝點擊下方按鈕分享給你的親朋好友。

譯者簡介:陳佳新 – 奇步應用共同創辦人,開發自有 App 和網站之外,也承包各式案件。譯有多本電腦書籍,包括 O’Reilly 出版的 iOS 、 Android 、 Agile 和 Google Cloud 等主題,也在報紙上寫過小說。現與妻兒居住在故鄉彰化。歡迎造訪 https://chibuapp.com ,來信請寄到 [email protected]

原文A Beginner’s Guide to In-App Purchase Programming in iOS 8

作者
Gabriel Theodoropoulos
資深軟體開發員,從事相關工作超過二十年,專門在不同的平台和各種程式語言去解決軟體開發問題。自2010年中,Gabriel專注在iOS程式的開發,利用教程與世界上每個角落的人分享知識。可以在Google+或推特關注 Gabriel。
評論
更多來自 AppCoda 中文版
增加營收獲利!為你的 App 加入內購買功能 (In-App Purchase)
in-app purchase

增加營收獲利!為你的 App 加入內購買功能 (In-App Purchase)

不可否認的是,最近這幾年,大部分開發者的收入都是仰賴 App 內購買。如果仔細想的話,答案更是不言自明。對於開發者而言, IAP 是一套很棒的工具,而對於使用者來說,則提供了很棒的選擇。有了 IAP ,兩造雙方都非常開心;開發者可以在 App Store 上面販售功能有限的免費 App
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。