iOS內(nèi)購(gòu) StoreKit文檔

成熟的小朋友要學(xué)會(huì)自己看文檔
置于為什么示例代碼是swift,因?yàn)楣俜轿臋n沒(méi)有給OC版本的,我也懶得改。
更新-后續(xù)的內(nèi)容有OC的代碼示例了。

StoreKit

支持應(yīng)用程序內(nèi)購(gòu)買以及與App Store的交互。


總覽

在應(yīng)用程序中使用StoreKit,可以提供以下功能和服務(wù):

  • In-App Purchase。 提供和推廣應(yīng)用內(nèi)購(gòu)買的內(nèi)容和服務(wù)。
  • Apple Music。 檢查用戶的Apple Music功能并提供訂閱。
  • Recommendations and reviews。 提供有關(guān)第三方內(nèi)容的建議,并使用戶能夠?qū)δ膽?yīng)用進(jìn)行評(píng)分和審查。

這里主要看內(nèi)購(gòu)。

In-App Purchase

通過(guò)在您的應(yīng)用內(nèi)購(gòu)買商品,為用戶提供其他內(nèi)容和服務(wù)。


應(yīng)用內(nèi)購(gòu)買可讓您為用戶提供購(gòu)買應(yīng)用內(nèi)內(nèi)容和功能的機(jī)會(huì)。 客戶可以在您的應(yīng)用程序中進(jìn)行購(gòu)買,也可以直接從App Store中進(jìn)行購(gòu)買。 有關(guān)在App Store中推廣產(chǎn)品的信息,請(qǐng)參閱Promoting Your In-App Purchases。

StoreKit框架代表您的應(yīng)用程序連接到App Store,以提示并安全地處理付款。 然后,框架通知您的應(yīng)用程序,該應(yīng)用程序?qū)⒔桓顿?gòu)買的產(chǎn)品。 要驗(yàn)證購(gòu)買,您可以通過(guò)App Store或設(shè)備在服務(wù)器上驗(yàn)證收據(jù)。 對(duì)于自動(dòng)續(xù)訂訂閱,App Store還可將關(guān)鍵訂閱事件通知您的服務(wù)器。

image.png

在App Store Connect中配置應(yīng)用內(nèi)購(gòu)買

要使用應(yīng)用內(nèi)購(gòu)買,必須首先在App Store Connect中配置產(chǎn)品。 在開(kāi)發(fā)應(yīng)用程序時(shí),您可以添加或刪除產(chǎn)品以及優(yōu)化或重新配置現(xiàn)有產(chǎn)品。 有關(guān)更多信息,請(qǐng)參閱Workflow for configuring in-app purchases

您還可以提供在多個(gè)平臺(tái)上運(yùn)行的應(yīng)用程序和應(yīng)用程序內(nèi)購(gòu)買作為一次購(gòu)買。 有關(guān)通用購(gòu)買的更多信息,請(qǐng)參閱App Store Connect Help。

了解產(chǎn)品類型

您可提供四種類型的 App 內(nèi)購(gòu)買項(xiàng)目:

  • 消耗型項(xiàng)目是一種使用一次之后即失效的項(xiàng)目。用戶可以多次購(gòu)買這類項(xiàng)目。
  • 非消耗型項(xiàng)目是一種用戶只需購(gòu)買一次的項(xiàng)目。這類項(xiàng)目不會(huì)過(guò)期。
  • 服務(wù)或內(nèi)容的自動(dòng)續(xù)期訂閱是一種用戶購(gòu)買一次之后,只要用戶不選擇取消,就會(huì)一直自動(dòng)續(xù)期的項(xiàng)目。
  • 服務(wù)或內(nèi)容的非續(xù)期訂閱有特定訪問(wèn)時(shí)限,不會(huì)自動(dòng)續(xù)期。用戶可以再次購(gòu)買這類項(xiàng)目。

您可以使用 StoreKit 跨設(shè)備同步和恢復(fù)非消耗型項(xiàng)目和自動(dòng)續(xù)期訂閱。當(dāng)用戶購(gòu)買自動(dòng)續(xù)期訂閱或非續(xù)期訂閱時(shí),您的 app 應(yīng)當(dāng)讓用戶能夠在所有設(shè)備上訪問(wèn)這一訂閱,并讓用戶能夠恢復(fù)以前購(gòu)買的項(xiàng)目。

官方API實(shí)例

為付款隊(duì)列設(shè)置交易觀察對(duì)象

通過(guò)添加觀察者,使您的應(yīng)用能夠接收和處理交易。


總覽

要在您的應(yīng)用中處理交易,您必須創(chuàng)建一個(gè)觀察者并將其添加到付款隊(duì)列中。 觀察者對(duì)象響應(yīng)新交易并將待處理交易隊(duì)列與App Store同步,并且支付隊(duì)列提示用戶授權(quán)支付。 您的應(yīng)用應(yīng)在應(yīng)用啟動(dòng)時(shí)添加交易觀察者,以確保您的應(yīng)用將盡快收到付款隊(duì)列通知。

創(chuàng)建觀察者

創(chuàng)建并構(gòu)建自定義觀察者類以處理對(duì)支付隊(duì)列的更改:

class StoreObserver: NSObject, SKPaymentTransactionObserver {
                ....
    //Initialize the store observer.
    override init() {
        super.init()
        //Other initialization here.
    }

    //Observe transaction updates.
    func paymentQueue(_ queue: SKPaymentQueue,updatedTransactions transactions: [SKPaymentTransaction]) {
        //Handle transaction states here.
    }
                ....
}

創(chuàng)建此觀察者類的實(shí)例以充當(dāng)付款隊(duì)列更改的觀察者:

let iapObserver = StoreObserver()

提示
考慮將您的觀察者創(chuàng)建為該類的共享實(shí)例,以便在任何其他類中進(jìn)行全局引用。 共享實(shí)例還可以保證對(duì)象的生存期,從而確保通過(guò)SKPaymentTransactionObserver協(xié)議進(jìn)行的回調(diào)始終由同一實(shí)例處理。

創(chuàng)建交易觀察者后,您可以將其添加到付款隊(duì)列中。
總結(jié)一下就是:自定義的實(shí)現(xiàn)了SKPaymentTransactionObserver協(xié)議的觀察對(duì)象

添加觀察者

當(dāng)您的應(yīng)用調(diào)用時(shí),StoreKit會(huì)將您的觀察者附加到隊(duì)列中:

SKPaymentQueue.default().add(iapObserver)

當(dāng)付款隊(duì)列的內(nèi)容在恢復(fù)或運(yùn)行應(yīng)用程序時(shí)發(fā)生更改時(shí),StoreKit可以自動(dòng)通知您的SKPaymentTransactionObserver實(shí)例。
實(shí)現(xiàn)交易觀察器:

import UIKit
import StoreKit

class AppDelegate: UIResponder, UIApplicationDelegate {
                ....
    // Attach an observer to the payment queue.
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        SKPaymentQueue.default().add(iapObserver)
        return true
    }

    // Called when the application is about to terminate.
    func applicationWillTerminate(_ application: UIApplication) {
        // Remove the observer.
        SKPaymentQueue.default().remove(iapObserver)
    }
                ....
}

重要的是在啟動(dòng)時(shí)在application:didFinishLaunchingWithOptions:中添加觀察者,以確保觀察者在您的應(yīng)用程序所有啟動(dòng)期間持續(xù)存在,接收所有付款隊(duì)列通知并繼續(xù)進(jìn)行可能在應(yīng)用程序外部處理的交易,例如:

  • 推薦的應(yīng)用內(nèi)購(gòu)買
  • 后臺(tái)訂閱續(xù)訂
  • 采購(gòu)中斷

觀察者必須是持久的,因此當(dāng)應(yīng)用程序發(fā)送到后臺(tái)時(shí),它不會(huì)被釋放。 只有持久的觀察者才能接收到您的應(yīng)用程序在后臺(tái)運(yùn)行時(shí)可能發(fā)生的交易,例如自動(dòng)續(xù)訂的續(xù)訂交易。

總結(jié)一下:在App啟動(dòng)的時(shí)候,把自定義的觀察對(duì)象放進(jìn)SKPaymentQueue隊(duì)列中,然后后續(xù)一系列的任務(wù)等待監(jiān)聽(tīng)回調(diào)交給觀察對(duì)象的協(xié)議方法處理。

提供和完成 App 內(nèi)購(gòu)買以及恢復(fù) App 內(nèi)購(gòu)買項(xiàng)目

在您的應(yīng)用中獲取,完成和還原交易。


總覽

應(yīng)用程序內(nèi)購(gòu)買允許用戶在您的應(yīng)用程序內(nèi)或使用StoreKit框架直接從App Store購(gòu)買虛擬商品。此示例代碼演示了如何檢索,顯示和還原應(yīng)用內(nèi)購(gòu)買。首先,您設(shè)置您的應(yīng)用程序以在啟動(dòng)時(shí)注冊(cè)并使用單事務(wù)隊(duì)列觀察器。交易隊(duì)列觀察者管理所有支付交易并處理所有交易狀態(tài)。確認(rèn)這是符合SKPaymentTransactionObserver協(xié)議的自定義類的共享實(shí)例。然后,在系統(tǒng)即將終止應(yīng)用程序時(shí)刪除事務(wù)觀察器。有關(guān)更多信息,請(qǐng)參見(jiàn)Setting Up the Transaction Observer for the Payment Queue。

此示例構(gòu)建了InAppPurchases應(yīng)用程序,支持iOS,macOS和tvOS平臺(tái)。啟動(dòng)后,InAppPurchases會(huì)在App Store中查詢Products.plist文件中保存的產(chǎn)品標(biāo)識(shí)符。 InAppPurchases會(huì)使用App Store的響應(yīng)來(lái)更新其UI,其中可能包括待售產(chǎn)品,無(wú)法識(shí)別的產(chǎn)品標(biāo)識(shí)符或兩者。該應(yīng)用程序還在其UI中顯示所有可用的已購(gòu)買和已恢復(fù)的付款交易。

配置示例代碼項(xiàng)目

在運(yùn)行和測(cè)試此示例之前,您需要:

  1. 從一個(gè)完整的應(yīng)用程序開(kāi)始,該應(yīng)用程序支持應(yīng)用程序內(nèi)購(gòu)買并在App Store Connect中配置了一些應(yīng)用程序內(nèi)購(gòu)買。有關(guān)更多信息,請(qǐng)參見(jiàn)第1步和第2步。
  2. 在App Store Connect中創(chuàng)建沙盒測(cè)試用戶帳戶。
  3. Xcode中打開(kāi)此示例,選擇要構(gòu)建的目標(biāo),然后將其捆綁ID更改為支持應(yīng)用內(nèi)購(gòu)買的捆綁ID。接下來(lái),選擇合適的團(tuán)隊(duì),讓Xcode自動(dòng)管理您的配置文件。
  4. 打開(kāi)示例中的ProductIds.plist文件,并使用您現(xiàn)有的應(yīng)用內(nèi)購(gòu)買商品ID更新其內(nèi)容。
  5. 對(duì)于iOStvOS設(shè)備,分別構(gòu)建和運(yùn)行InAppPurchasesInAppPurchasestvOS目標(biāo),該示例用于構(gòu)建InAppPurchases。
  6. 對(duì)于macOS,在構(gòu)建InAppPurchasesmacOS目標(biāo)之前,請(qǐng)退出Mac App Store。構(gòu)建目標(biāo),然后首次從Finder啟動(dòng)生成的應(yīng)用以獲取收據(jù)。有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)Testing In-App Purchases with Sandbox。
  7. InAppPurchases應(yīng)用程序在啟動(dòng)時(shí)向App Store查詢ProductIds.plist中包含的產(chǎn)品標(biāo)識(shí)符。成功后,它將顯示可在App Store中出售的產(chǎn)品列表。點(diǎn)按列表中的任何產(chǎn)品以進(jìn)行購(gòu)買。當(dāng)系統(tǒng)提示您進(jìn)行購(gòu)買驗(yàn)證時(shí),請(qǐng)使用在步驟2中創(chuàng)建的測(cè)試用戶帳戶。當(dāng)產(chǎn)品請(qǐng)求失敗時(shí),出于各種原因,請(qǐng)參閱 invalidProductIdentifiers的討論,原因是App Store可能會(huì)返回?zé)o效的產(chǎn)品標(biāo)識(shí)符。

顯示可用本地化定價(jià)出售的產(chǎn)品

該示例配置了應(yīng)用程序,以便在展示要出售的產(chǎn)品之前確認(rèn)用戶已被授權(quán)在設(shè)備上付款。

var isAuthorizedForPayments: Bool {
    return SKPaymentQueue.canMakePayments()
}

應(yīng)用確認(rèn)授權(quán)后,就會(huì)向App Store發(fā)送產(chǎn)品請(qǐng)求,以從App Store獲取本地化的產(chǎn)品信息。 查詢App Store可確保該App僅向用戶顯示可供購(gòu)買的產(chǎn)品。 該應(yīng)用程序使用與其希望在其UI中出售的產(chǎn)品相關(guān)聯(lián)的產(chǎn)品標(biāo)識(shí)符列表來(lái)初始化產(chǎn)品請(qǐng)求。 確保對(duì)產(chǎn)品請(qǐng)求對(duì)象使用強(qiáng)引用持有,系統(tǒng)可能會(huì)在請(qǐng)求完成之前將其釋放。

fileprivate func fetchProducts(matchingIdentifiers identifiers: [String]) {
    // Create a set for the product identifiers.
    let productIdentifiers = Set(identifiers)
    
    // Initialize the product request with the above identifiers.
    productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
    productRequest.delegate = self
    
    // Send the request to the App Store.
    productRequest.start()
}

App Store使用 SKProductsResponse對(duì)象響應(yīng)產(chǎn)品請(qǐng)求。 其產(chǎn)品屬性包含有關(guān)可在App Store中實(shí)際購(gòu)買的所有產(chǎn)品的信息。 該應(yīng)用程序使用此屬性來(lái)更新其UI。 響應(yīng)的invalidProductIdentifiers屬性包含App Store無(wú)法識(shí)別的所有產(chǎn)品標(biāo)識(shí)符。 出于各種原因,請(qǐng)參閱invalidProductIdentifiers的討論,為什么App Store可能會(huì)返回?zé)o效的產(chǎn)品標(biāo)識(shí)符。

// products contains products whose identifiers have been recognized by the App Store. As such, they can be purchased.
if !response.products.isEmpty {
    availableProducts = response.products
}

// invalidProductIdentifiers contains all product identifiers not recognized by the App Store.
if !response.invalidProductIdentifiers.isEmpty {
    invalidProductIdentifiers = response.invalidProductIdentifiers
}

要在用戶界面中顯示產(chǎn)品的價(jià)格,請(qǐng)使用App Store返回的語(yǔ)言環(huán)境和貨幣。 例如,考慮一個(gè)登錄法國(guó)應(yīng)用程序商店并且其設(shè)備使用美國(guó)語(yǔ)言環(huán)境的用戶。 嘗試購(gòu)買產(chǎn)品時(shí),App Store會(huì)以歐元顯示產(chǎn)品價(jià)格。 因此,轉(zhuǎn)換和顯示以美元表示的產(chǎn)品價(jià)格以使其與用戶界面中的設(shè)備區(qū)域設(shè)置相匹配是不正確的。

extension SKProduct {
    /// - returns: The cost of the product formatted in the local currency.
    var regularPrice: String? {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = self.priceLocale
        return formatter.string(from: self.price)
    }
}

與應(yīng)用互動(dòng)以購(gòu)買產(chǎn)品

在用戶界面中點(diǎn)擊任何可出售的產(chǎn)品進(jìn)行購(gòu)買。 該應(yīng)用程序允許用戶還原非消耗性產(chǎn)品和自動(dòng)續(xù)訂的訂閱。 使用還原按鈕和設(shè)置>還原所有可還原的購(gòu)買分別在iOStvOS版本的應(yīng)用程序中實(shí)現(xiàn)此功能。 選擇商店>恢復(fù)菜單項(xiàng)以在應(yīng)用的macOS版本中恢復(fù)購(gòu)買。 點(diǎn)擊任何購(gòu)買的商品都會(huì)顯示購(gòu)買信息,例如產(chǎn)品標(biāo)識(shí)符,交易標(biāo)識(shí)符和交易日期。 當(dāng)購(gòu)買的商品是托管產(chǎn)品時(shí),購(gòu)買信息還包括其內(nèi)容標(biāo)識(shí)符,內(nèi)容版本和內(nèi)容長(zhǎng)度。 當(dāng)購(gòu)買恢復(fù)后,購(gòu)買信息還將包含其原始交易的標(biāo)識(shí)符和日期。

處理付款交易狀態(tài)

當(dāng)付款隊(duì)列中有待處理的交易時(shí),StoreKit會(huì)通過(guò)調(diào)用應(yīng)用的 paymentQueue:updatedTransactions:方法來(lái)通知應(yīng)用的交易觀察者。 每個(gè)交易都有五個(gè)可能的狀態(tài),包括SKPaymentTransactionStatePurchasing,SKPaymentTransactionStatePurchased,SKPaymentTransactionStateFailed,SKPaymentTransactionStateRestoredSKPaymentTransactionStateDeferred; 有關(guān)更多信息,請(qǐng)參見(jiàn)SKPaymentTransactionState 應(yīng)用程序應(yīng)確保其觀察者的 paymentQueue:updatedTransactions:可以隨時(shí)響應(yīng)任何一種狀態(tài)。 提供Apple托管的產(chǎn)品時(shí),他們應(yīng)該在其觀察者上實(shí)現(xiàn) paymentQueue:updatedDownloads:`方法。

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transaction in transactions {
        switch transaction.transactionState {
        case .purchasing: break
        // Do not block the UI. Allow the user to continue using the app.
        case .deferred: print(Messages.deferred)
        // The purchase was successful.
        case .purchased: handlePurchased(transaction)
        // The transaction failed.
        case .failed: handleFailed(transaction)
        // There're restored products.
        case .restored: handleRestored(transaction)
        @unknown default: fatalError(Messages.unknownPaymentTransaction)
        }
    }
}

當(dāng)事務(wù)失敗時(shí),檢查其錯(cuò)誤屬性以確定發(fā)生了什么。 僅顯示代碼與paymentCancelled不同的錯(cuò)誤。

// Do not send any notifications when the user cancels the purchase.
if (transaction.error as? SKError)?.code != .paymentCancelled {
    DispatchQueue.main.async {
        self.delegate?.storeObserverDidReceiveMessage(message)
    }
}

當(dāng)用戶推遲交易時(shí),應(yīng)用應(yīng)允許他們?cè)诘却齋toreKit更新交易時(shí)繼續(xù)使用UI。

恢復(fù)已完成的購(gòu)買

當(dāng)用戶購(gòu)買非消耗品,自動(dòng)更新訂閱或非更新訂閱時(shí),他們希望它們可以在所有設(shè)備上無(wú)限期可用。 有關(guān)更多信息,請(qǐng)參見(jiàn)In-App Purchase。 該應(yīng)用程序提供了一個(gè)UI,允許用戶恢復(fù)其過(guò)去的購(gòu)買記錄。

@IBAction func restore(_ sender: UIBarButtonItem) {
    // Calls StoreObserver to restore all restorable purchases.
    StoreObserver.shared.restore()
}

使用SKPaymentQueuerestoreCompletedTransactions還原非消耗性和自動(dòng)續(xù)訂的訂閱。 StoreKit通過(guò)調(diào)用每個(gè)已恢復(fù)的交易的SKPaymentTransactionStateRestored交易狀態(tài)的paymentQueue:updatedTransactions:通知應(yīng)用程序的交易觀察者。 如果還原失敗,請(qǐng)參閱restoreCompletedTransactions的討論以獲取有關(guān)如何解決該問(wèn)題的詳細(xì)信息。 恢復(fù)非續(xù)訂的訂閱不在此示例的范圍內(nèi)。

提供內(nèi)容并完成交易

在收到狀態(tài)為SKPaymentTransactionStatePurchasedSKPaymentTransactionStateRestored的交易后,應(yīng)用必須交付內(nèi)容或解鎖購(gòu)買的功能。這些狀態(tài)表明App Store已從用戶處收到產(chǎn)品付款。當(dāng)購(gòu)買的產(chǎn)品包含來(lái)自App Store的托管內(nèi)容時(shí),請(qǐng)致電SKPaymentQueue的startDownloads:下載內(nèi)容。

未完成的交易留在付款隊(duì)列中。在每次從后臺(tái)啟動(dòng)或從后臺(tái)恢復(fù)運(yùn)行之前,StoreKit都會(huì)調(diào)用該應(yīng)用程序的永久觀察者的paymentQueue:updatedTransactions:直到應(yīng)用程序完成這些交易。結(jié)果,App Store可能會(huì)反復(fù)提示用戶對(duì)他們的購(gòu)買進(jìn)行身份驗(yàn)證,或阻止他們從該應(yīng)用程序購(gòu)買產(chǎn)品。

對(duì)狀態(tài)為SKPaymentTransactionStateFailed,SKPaymentTransactionStatePurchasedSKPaymentTransactionStateRestored的事務(wù)調(diào)用finishTransaction:以將其從隊(duì)列中刪除。完成的交易無(wú)法恢復(fù)。因此,應(yīng)用程序必須提供購(gòu)買的內(nèi)容,下載產(chǎn)品的所有Apple托管內(nèi)容或完成購(gòu)買過(guò)程,然后才能完成交易。

// Finish the successful transaction.
SKPaymentQueue.default().finishTransaction(transaction)

SKPaymentQueue 付款隊(duì)列

由App Store處理的付款交易隊(duì)列。

@interface SKPaymentQueue : NSObject

總覽

付款隊(duì)列與App Store通信并顯示用戶界面,以便用戶可以授權(quán)付款。隊(duì)列的內(nèi)容在應(yīng)用程序啟動(dòng)之間保持不變。

要處理付款,請(qǐng)首先將至少一個(gè)觀察者對(duì)象(SKPaymentTransactionObserver)添加到隊(duì)列中(請(qǐng)參閱addTransactionObserver:`)。然后,為用戶要購(gòu)買的商品添加支付對(duì)象(SKPayment)。每次添加付款對(duì)象時(shí),隊(duì)列都會(huì)創(chuàng)建一個(gè)交易對(duì)象(SKPaymentTransaction)以處理該付款并將其排隊(duì)。付款完成后,隊(duì)列更新交易對(duì)象,然后調(diào)用任何觀察者對(duì)象向他們提供更新的交易。您的觀察者應(yīng)處理該事務(wù),然后將其從隊(duì)列中刪除。

您用于處理已處理交易的確切機(jī)制取決于應(yīng)用程序的設(shè)計(jì)和所購(gòu)買的產(chǎn)品。以下是一些常見(jiàn)示例:

  1. 如果產(chǎn)品是您的應(yīng)用程序中已內(nèi)置的功能,則您的應(yīng)用程序?qū)⒂迷摴δ軄?lái)處理交易。
  2. 如果產(chǎn)品包含App Store提供的可下載內(nèi)容,則您的應(yīng)用將從交易中檢索SKDownload對(duì)象,并要求付款隊(duì)列下載它們。您在創(chuàng)建產(chǎn)品信息時(shí)將實(shí)際的內(nèi)容文件提供給App Store并提供給App Store Connect。
  3. 如果產(chǎn)品代表您自己的服務(wù)器提供的可下載內(nèi)容,則您的應(yīng)用程序可能會(huì)打開(kāi)與服務(wù)器的網(wǎng)絡(luò)連接并從那里下載內(nèi)容。

有關(guān)設(shè)計(jì)應(yīng)用程序的付款處理部分的更多信息,請(qǐng)參閱應(yīng)用程序內(nèi)購(gòu)買編程指南。


Product Information 產(chǎn)品信息

加載應(yīng)用內(nèi)商品的唯一標(biāo)識(shí)符,以便從App Store檢索商品信息。

應(yīng)用內(nèi)購(gòu)買流程可以分為三個(gè)階段。 在購(gòu)買過(guò)程的第一階段,您的應(yīng)用程序從App Store檢索有關(guān)其產(chǎn)品的信息,向用戶顯示其商店UI,并讓用戶選擇產(chǎn)品。 當(dāng)用戶在您的應(yīng)用商店中選擇產(chǎn)品時(shí),您的應(yīng)用會(huì)請(qǐng)求付款,最后,您的應(yīng)用會(huì)交付該產(chǎn)品。 圖1中突出顯示了應(yīng)用程序和App Store在第一階段執(zhí)行的步驟。

第一階段執(zhí)行的步驟

要開(kāi)始購(gòu)買過(guò)程,您的應(yīng)用必須知道其產(chǎn)品標(biāo)識(shí)符,以便它可以從App Store檢索有關(guān)產(chǎn)品的信息,并將其商店UI呈現(xiàn)給用戶。 應(yīng)用中出售的每個(gè)產(chǎn)品都有唯一的產(chǎn)品標(biāo)識(shí)符。 當(dāng)您創(chuàng)建新的應(yīng)用內(nèi)購(gòu)買產(chǎn)品時(shí),您可以在App Store Connect中提供此值(有關(guān)更多信息,請(qǐng)參閱 Create an In-App Purchase)。 您的應(yīng)用使用這些產(chǎn)品標(biāo)識(shí)符來(lái)獲取有關(guān)在App Store中可出售的產(chǎn)品的信息(例如定價(jià)),并在用戶購(gòu)買這些產(chǎn)品時(shí)提交付款請(qǐng)求。

有幾種策略可用于在應(yīng)用程序中存儲(chǔ)產(chǎn)品標(biāo)識(shí)符列表,例如嵌入到應(yīng)用程序捆綁包中或存儲(chǔ)在服務(wù)器上。 然后,您可以通過(guò)在應(yīng)用程序包中本地讀取產(chǎn)品標(biāo)識(shí)符或從服務(wù)器中獲取它們來(lái)檢索產(chǎn)品標(biāo)識(shí)符。 選擇最能滿足您應(yīng)用程序需求的方法。

注意
沒(méi)有運(yùn)行時(shí)機(jī)制可以獲取在App Store Connect中為特定應(yīng)用配置的所有產(chǎn)品的列表。 您負(fù)責(zé)管理應(yīng)用程序的產(chǎn)品列表,并將該信息提供給您的應(yīng)用程序。 如果您需要管理大量產(chǎn)品,請(qǐng)考慮使用App Store Connect中的批量XML上傳/下載功能。

從應(yīng)用程序包中檢索產(chǎn)品ID

在以下情況下,將產(chǎn)品標(biāo)識(shí)符嵌入到您的應(yīng)用程序包中:

  1. 您的應(yīng)用有固定的應(yīng)用內(nèi)購(gòu)買產(chǎn)品列表。 例如,通過(guò)應(yīng)用內(nèi)購(gòu)買來(lái)刪除廣告或解鎖功能的應(yīng)用可以將產(chǎn)品標(biāo)識(shí)符列表嵌入應(yīng)用包中。
  2. 您希望用戶更新應(yīng)用程序以查看新的應(yīng)用程序內(nèi)購(gòu)買產(chǎn)品。
  3. 該應(yīng)用程序或產(chǎn)品不需要服務(wù)器。

在您的應(yīng)用程序捆綁包中包含一個(gè)屬性列表文件,其中包含一系列產(chǎn)品標(biāo)識(shí)符,例如:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <string>com.example.level1</string>
    <string>com.example.level2</string>
    <string>com.example.rocket_car</string>
</array>
</plist>

要從屬性列表中獲取產(chǎn)品標(biāo)識(shí)符,請(qǐng)?jiān)趹?yīng)用程序捆綁包中找到文件并閱讀。

NSURL *url = [[NSBundle mainBundle] URLForResource:@"product_ids"
                                     withExtension:@"plist"];
NSArray *productIdentifiers = [NSArray arrayWithContentsOfURL:url];

從服務(wù)器中檢索產(chǎn)品ID

在以下情況下,存儲(chǔ)來(lái)自服務(wù)器的產(chǎn)品標(biāo)識(shí)符:

  1. 您可以經(jīng)常更新應(yīng)用程序內(nèi)產(chǎn)品列表,而無(wú)需更新應(yīng)用程序。 例如,支持其他級(jí)別或角色的游戲應(yīng)從您的服務(wù)器獲取產(chǎn)品標(biāo)識(shí)符列表。
  2. 產(chǎn)品包括交付的內(nèi)容。
  3. 您的應(yīng)用或產(chǎn)品需要服務(wù)器。

使用產(chǎn)品標(biāo)識(shí)符在您的服務(wù)器上托管JSON文件。 例如,以下JSON文件包含三個(gè)產(chǎn)品ID:

[
    "com.example.level1",
    "com.example.level2",
    "com.example.rocket_car"
]

要從服務(wù)器獲取產(chǎn)品標(biāo)識(shí)符,請(qǐng)獲取并讀取JSON文件。

- (void)fetchProductIdentifiersFromURL:(NSURL *)url delegate:(id)delegate
{
    dispatch_queue_t global_queue =
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(global_queue, ^{
        NSError *err;
        NSData *jsonData = [NSData dataWithContentsOfURL:url
                                                 options:NULL
                                                   error:&err];
        if (!jsonData) { /* Handle the error. */ }

        NSArray *productIdentifiers = [NSJSONSerialization
            JSONObjectWithData:jsonData options:NULL error:&err];
        if (!productIdentifiers) { /* Handle the error. */ }

        dispatch_queue_t main_queue = dispatch_get_main_queue();
        dispatch_async(main_queue, ^{
            [delegate displayProducts:productIdentifiers]; // Custom method.
        });
    });
}

考慮對(duì)JSON文件進(jìn)行版本控制,以便將來(lái)的應(yīng)用程序版本可以更改其結(jié)構(gòu),而不會(huì)破壞應(yīng)用程序的舊版本。 例如,您可以命名使用舊結(jié)構(gòu)products_v1.json的文件和使用新結(jié)構(gòu)products_v2.json的文件。 如果您的JSON文件比示例中的簡(jiǎn)單數(shù)組復(fù)雜,則此功能特別有用。

為了確保您的應(yīng)用程序保持響應(yīng)狀態(tài),請(qǐng)使用后臺(tái)線程下載JSON文件并提取產(chǎn)品標(biāo)識(shí)符列表。 為了使傳輸?shù)臄?shù)據(jù)最少,請(qǐng)使用標(biāo)準(zhǔn)的HTTP緩存機(jī)制,例如Last-Modified和If-Modified-Since`標(biāo)頭。

加載所有應(yīng)用內(nèi)商品標(biāo)識(shí)后,將其傳遞到商品信息請(qǐng)求中,并發(fā)送到App Store。 有

從App Store獲取產(chǎn)品信息

在您的應(yīng)用中檢索有關(guān)待售產(chǎn)品的最新信息,以顯示給用戶。

總覽

為了確保您的用戶只能看到實(shí)際可購(gòu)買的產(chǎn)品,請(qǐng)?jiān)陲@示應(yīng)用商店的用戶界面之前查詢App Store。 您將需要所有應(yīng)用程序產(chǎn)品標(biāo)識(shí)符的列表; 有關(guān)獲取此列表的更多信息,請(qǐng)參閱加載應(yīng)用內(nèi)商品標(biāo)識(shí)。

請(qǐng)求產(chǎn)品信息

要查詢App Store,請(qǐng)創(chuàng)建一個(gè)products requestSKProductsRequest),并使用您的產(chǎn)品標(biāo)識(shí)符列表對(duì)其進(jìn)行初始化。 確保對(duì)請(qǐng)求對(duì)象有嚴(yán)格的引用; 否則,系統(tǒng)可能會(huì)在請(qǐng)求完成之前取消分配該請(qǐng)求。

products request檢索有關(guān)有效產(chǎn)品的信息以及無(wú)效產(chǎn)品標(biāo)識(shí)符的列表,然后調(diào)用其委托以處理結(jié)果。 委托必須實(shí)現(xiàn)SKProductsRequestDelegate協(xié)議以處理來(lái)自App Store的響應(yīng)。 這是這兩段代碼的簡(jiǎn)單實(shí)現(xiàn):

// Custom method.
- (void)validateProductIdentifiers:(NSArray *)productIdentifiers
{
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc]
        initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]];

    // Keep a strong reference to the request.
    self.request = productsRequest;
    productsRequest.delegate = self;
    [productsRequest start];
}

// SKProductsRequestDelegate protocol method.
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
    self.products = response.products;

    for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
        // Handle any invalid product identifiers.
    }

    [self displayStoreUI]; // Custom method.
}

保留對(duì)返回給委托的產(chǎn)品對(duì)象數(shù)組(SKProduct)的引用,因?yàn)楫?dāng)用戶購(gòu)買產(chǎn)品時(shí),您將需要相應(yīng)的產(chǎn)品對(duì)象來(lái)創(chuàng)建付款請(qǐng)求。 如果在您的應(yīng)用程序上出售的產(chǎn)品列表可能會(huì)發(fā)生變化,例如當(dāng)您添加或從銷售中刪除產(chǎn)品時(shí),請(qǐng)考慮創(chuàng)建一個(gè)自定義類,該類封裝對(duì)產(chǎn)品對(duì)象的引用以及其他信息,例如圖片或描述文字 從服務(wù)器上獲取的信息。 有關(guān)付款請(qǐng)求的更多信息,請(qǐng)參閱Requesting a Payment from the App Store

解決無(wú)效的產(chǎn)品ID

App Store對(duì)您的產(chǎn)品請(qǐng)求的回復(fù)中,無(wú)效的產(chǎn)品ID通常表示您的應(yīng)用的產(chǎn)品ID列表中存在錯(cuò)誤。 無(wú)效的產(chǎn)品ID也可能意味著您在App Store Connect中配置了不正確的產(chǎn)品。 可行的用戶界面和深入的日志記錄可以幫助您更輕松地解決此類問(wèn)題:

  1. 在生產(chǎn)版本中,通過(guò)顯示應(yīng)用程序其余的商店用戶界面并忽略無(wú)效的產(chǎn)品,以使其正常運(yùn)行。
  2. 在開(kāi)發(fā)版本中,顯示錯(cuò)誤以引起注意該問(wèn)題。
  3. 在生產(chǎn)和開(kāi)發(fā)版本中,都使用NSLog將消息寫(xiě)入控制臺(tái),以便您記錄無(wú)效標(biāo)識(shí)符。
  4. 如果您的應(yīng)用程序是從服務(wù)器上獲取列表的,則還可以定義一種日志記錄機(jī)制,以使您的應(yīng)用程序?qū)o(wú)效標(biāo)識(shí)符的列表發(fā)送回服務(wù)器。

購(gòu)買- 從App Store請(qǐng)求付款

用戶選擇要購(gòu)買的產(chǎn)品時(shí),向App Store提交付款請(qǐng)求。

總覽

在顯示應(yīng)用程序的商店用戶界面后,用戶可以在應(yīng)用程序內(nèi)進(jìn)行購(gòu)買。 當(dāng)用戶選擇產(chǎn)品時(shí),您的應(yīng)用會(huì)創(chuàng)建一個(gè)付款請(qǐng)求并將其提交到App Store。

實(shí)施應(yīng)用內(nèi)購(gòu)買流程可以分為三個(gè)階段。 在第一階段,您的應(yīng)用將檢索產(chǎn)品信息。 然后,當(dāng)用戶在您的應(yīng)用商店中選擇產(chǎn)品時(shí),您的應(yīng)用就會(huì)請(qǐng)求付款。 最后,您的應(yīng)用可以交付產(chǎn)品。 本節(jié)詳細(xì)介紹了應(yīng)用程序和App Store在第二階段執(zhí)行的步驟,如圖1所示。


圖1

創(chuàng)建付款請(qǐng)求

當(dāng)用戶選擇要購(gòu)買的產(chǎn)品時(shí),請(qǐng)使用相應(yīng)的SKProduct對(duì)象創(chuàng)建付款請(qǐng)求,并根據(jù)需要設(shè)置數(shù)量,如下所示。 產(chǎn)品對(duì)象來(lái)自您應(yīng)用的產(chǎn)品請(qǐng)求所返回的一系列產(chǎn)品,如從Fetching Product Information from the App Store。

SKProduct *product = <# Product returned by a products request. #>;
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.quantity = 2;

提交付款請(qǐng)求

通過(guò)將您的付款請(qǐng)求添加到付款隊(duì)列,將其提交到App Store。 如果您將付款對(duì)象多次添加到隊(duì)列中,則會(huì)多次提交到App Store,向用戶收費(fèi)并要求您的應(yīng)用每次交付產(chǎn)品。

[[SKPaymentQueue defaultQueue] addPayment:payment];

對(duì)于您的應(yīng)用程序提交的每個(gè)付款請(qǐng)求,它都會(huì)收到相應(yīng)的交易要處理。 有關(guān)交易和付款隊(duì)列的更多信息,請(qǐng)參閱Processing a Transaction。

對(duì)于自動(dòng)續(xù)訂訂閱,您可以針對(duì)確定為有資格接收?qǐng)?bào)價(jià)的用戶提交帶有訂閱報(bào)價(jià)的付款請(qǐng)求。 有關(guān)更多信息,請(qǐng)參閱Implementing Promotional Offers in Your App。


交易處理

注冊(cè)事交易列觀察器以從App Store獲取和處理交易更新。

總覽

實(shí)施應(yīng)用內(nèi)購(gòu)買流程可以分為三個(gè)階段。 您首先要檢索產(chǎn)品信息,然后通過(guò)將其添加到付款隊(duì)列中來(lái)發(fā)送付款請(qǐng)求。 最后,您的應(yīng)用可以交付成功交易的產(chǎn)品。 本節(jié)詳細(xì)介紹了您的應(yīng)用程序和App Store在最后階段執(zhí)行的步驟,如圖1所示。

在處理付款請(qǐng)求后,App Store會(huì)調(diào)用交易隊(duì)列觀察器。 然后,您的應(yīng)用會(huì)記錄有關(guān)購(gòu)買的信息以供將來(lái)啟動(dòng),下載購(gòu)買的內(nèi)容并將交易標(biāo)記為完成。

圖1通過(guò)監(jiān)視交易隊(duì)列以交付產(chǎn)品來(lái)完成購(gòu)買過(guò)程


監(jiān)視隊(duì)列中的交易

交易隊(duì)列在讓您的應(yīng)用通過(guò)StoreKit框架與App Store進(jìn)行通信中起著核心作用。您將工作添加到需要應(yīng)用商店執(zhí)行的隊(duì)列中,例如要處理的付款請(qǐng)求。當(dāng)交易狀態(tài)發(fā)生變化時(shí)(例如,付款請(qǐng)求成功時(shí)),StoreKit會(huì)調(diào)用應(yīng)用的交易隊(duì)列觀察器。您決定哪個(gè)類充當(dāng)觀察員。在非常小的應(yīng)用程序中,您可以處理應(yīng)用程序委托中的所有StoreKit邏輯,包括觀察交易隊(duì)列。但是,在大多數(shù)應(yīng)用中,您創(chuàng)建一個(gè)單獨(dú)的類來(lái)處理此觀察者邏輯以及應(yīng)用的其余存儲(chǔ)邏輯。觀察者必須符合SKPaymentTransactionObserver協(xié)議。

通過(guò)添加觀察者,您的應(yīng)用無(wú)需持續(xù)輪詢其活動(dòng)交易的狀態(tài)。您的應(yīng)用將交易隊(duì)列用于支付請(qǐng)求,下載Apple托管的內(nèi)容以及確定訂閱已續(xù)訂。

始終在啟動(dòng)應(yīng)用程序后立即注冊(cè)事務(wù)隊(duì)列觀察器,如下所示。有關(guān)更多指導(dǎo),請(qǐng)參閱Setting Up the Transaction Observer for the Payment Queue.

- (BOOL)application:(UIApplication *)application
 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    /* ... */

    [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
}

確保觀察者隨時(shí)準(zhǔn)備處理交易,不僅是在將交易添加到隊(duì)列中之后。 例如,如果用戶在進(jìn)入隧道之前立即在您的應(yīng)用程序中購(gòu)買了商品,則在沒(méi)有網(wǎng)絡(luò)連接的情況下,您的應(yīng)用程序可能無(wú)法傳遞購(gòu)買的內(nèi)容。 下次您的應(yīng)用程序啟動(dòng)時(shí),StoreKit再次調(diào)用您的交易隊(duì)列觀察器,并且您的應(yīng)用程序應(yīng)處理交易并交付購(gòu)買的內(nèi)容。 同樣,如果您的應(yīng)用程序未能將交易標(biāo)記為已完成,則StoreKit會(huì)在您的應(yīng)用程序每次啟動(dòng)時(shí)調(diào)用觀察者,直到事務(wù)完成為止。

在您的交易隊(duì)列觀察器上實(shí)現(xiàn)paymentQueue:updatedTransactions:方法。 當(dāng)交易狀態(tài)改變時(shí),例如在處理付款請(qǐng)求時(shí),StoreKit調(diào)用此方法。 交易狀態(tài)會(huì)告訴您您的應(yīng)用需要執(zhí)行什么操作,如表1所示。

狀態(tài) 對(duì)應(yīng)的處理方式
SKPaymentTransactionStatePurchasing 更新您的用戶界面以反映進(jìn)行中的狀態(tài),然后等待再次調(diào)用。
SKPaymentTransactionStateDeferred 更新您的用戶界面以反映延遲狀態(tài),然后等待再次調(diào)用。
SKPaymentTransactionStateFailed 使用error屬性的值向用戶顯示消息。 有關(guān)錯(cuò)誤常量的列表,請(qǐng)參見(jiàn)SKErrorDomain
SKPaymentTransactionStatePurchased 通常通過(guò)解鎖功能或提供內(nèi)容來(lái)提供購(gòu)買的功能。
SKPaymentTransactionStateRestored 恢復(fù)以前購(gòu)買的功能。

隊(duì)列中的事務(wù)可以按任何順序更改狀態(tài)。 您的應(yīng)用需要隨時(shí)準(zhǔn)備就緒,可以處理任何活動(dòng)交易。 根據(jù)每個(gè)交易的交易狀態(tài)對(duì)其進(jìn)行操作,如本例所示:

- (void)paymentQueue:(SKPaymentQueue *)queue
 updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            // Call the appropriate custom method for the transaction state.
            case SKPaymentTransactionStatePurchasing:
                [self showTransactionAsInProgress:transaction deferred:NO];
                break;
            case SKPaymentTransactionStateDeferred:
                [self showTransactionAsInProgress:transaction deferred:YES];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            default:
                // For debugging
                NSLog(@"Unexpected transaction state %@", @(transaction.transactionState));
                break;
        }
    }
}

更新應(yīng)用程序的用戶界面以反映交易的變化

為了使您的用戶界面在等待時(shí)保持最新?tīng)顟B(tài),事務(wù)隊(duì)列觀察器可以從SKPaymentTransactionObserver協(xié)議中實(shí)現(xiàn)可選方法,如下所示:

  • 當(dāng)StoreKit從隊(duì)列中刪除事務(wù)時(shí),StoreKit會(huì)調(diào)用paymentQueue:removedTransactions:方法。 在實(shí)施此方法時(shí),請(qǐng)從應(yīng)用的用戶界面中刪除相應(yīng)的項(xiàng)。
  • 當(dāng)StoreKit完成還原交易時(shí),將根據(jù)其是否存在錯(cuò)誤來(lái)調(diào)用paymentQueueRestoreCompletedTransactionsFinished:paymentQueue:restoreCompletedTransactionsFailedWithError:方法。 在實(shí)施這些方法時(shí),請(qǐng)更新應(yīng)用的用戶界面以反映成功或失敗。

對(duì)于成功處理的交易,您的應(yīng)用應(yīng)驗(yàn)證與交易關(guān)聯(lián)的收據(jù),以驗(yàn)證用戶購(gòu)買的商品并相應(yīng)地解鎖內(nèi)容。 有關(guān)在服務(wù)器端驗(yàn)證收據(jù)的更多信息,請(qǐng)參閱Validating Receipts with the App Store


選擇收據(jù)驗(yàn)證技術(shù)

選擇適用于您的應(yīng)用的收據(jù)驗(yàn)證類型。

總覽

App Store收據(jù)提供了應(yīng)用程序銷售或在應(yīng)用程序內(nèi)進(jìn)行的任何購(gòu)買的記錄,您可以通過(guò)向應(yīng)用程序或服務(wù)器添加收據(jù)驗(yàn)證碼來(lái)驗(yàn)證購(gòu)買的內(nèi)容。 收據(jù)驗(yàn)證需要了解安全編碼技術(shù),才能采用對(duì)您的應(yīng)用程序安全且獨(dú)特的解決方案。

選擇驗(yàn)證技術(shù)

有兩種驗(yàn)證收據(jù)真實(shí)性的方法:

  1. 建議在設(shè)備上進(jìn)行本地設(shè)備上的收據(jù)驗(yàn)證,以驗(yàn)證帶有應(yīng)用內(nèi)購(gòu)買的應(yīng)用的收據(jù)簽名。
  2. 建議通過(guò)App Store進(jìn)行服務(wù)器端收據(jù)驗(yàn)證,以持久保存應(yīng)用內(nèi)購(gòu)買,以維護(hù)和管理購(gòu)買記錄。

比較方法并確定最適合您的應(yīng)用程序和基礎(chǔ)架構(gòu)的方法。 您也可以選擇實(shí)施兩種方法。

消耗性的應(yīng)用內(nèi)購(gòu)買將保留在收據(jù)中,直到您調(diào)用finishTransaction:為止。 如果需要,維護(hù)和管理服務(wù)器上消耗品的記錄。 非消耗品,自動(dòng)續(xù)訂的訂閱項(xiàng)目和非續(xù)訂的訂閱項(xiàng)目會(huì)無(wú)限期保留在收據(jù)中。 對(duì)于自動(dòng)續(xù)訂訂閱管理,服務(wù)器端收據(jù)驗(yàn)證比設(shè)備上的收據(jù)驗(yàn)證具有關(guān)鍵優(yōu)勢(shì)。

-- 本地設(shè)備上驗(yàn)證 服務(wù)器端驗(yàn)證
驗(yàn)證收據(jù)的真實(shí)性 Yes Yes
包括續(xù)訂交易 Yes Yes
包括其他用戶訂閱信息 No Yes
處理續(xù)簽而無(wú)需客戶依賴性 No Yes
Resistant to device clock change No Yes

注意
為了使設(shè)備上的收據(jù)驗(yàn)證包括續(xù)訂交易,必須有互聯(lián)網(wǎng)連接才能刷新收據(jù)。

有關(guān)為包含自動(dòng)可更新訂閱產(chǎn)品的應(yīng)用程序?qū)嵤┦論?jù)驗(yàn)證的更多信息,請(qǐng)參見(jiàn) WWDC 2018 > Engineering Subscriptions。

驗(yàn)證收據(jù)

本地驗(yàn)證需要代碼讀取和驗(yàn)證PKCS#7簽名,還需要代碼解析和驗(yàn)證簽名的有效負(fù)載。 通過(guò)App Store進(jìn)行驗(yàn)證需要在您的應(yīng)用程序與服務(wù)器之間建立安全連接,并在服務(wù)器上進(jìn)行編碼以通過(guò)App Store驗(yàn)證收據(jù)。 有關(guān)服務(wù)器端驗(yàn)證的更多信息,請(qǐng)參閱Validating Receipts with the App Store

盡管收據(jù)通常會(huì)在完成購(gòu)買或恢復(fù)購(gòu)買后立即更新,但是當(dāng)應(yīng)用程序未運(yùn)行時(shí),更改可能會(huì)在其他時(shí)間發(fā)生。 如有必要,請(qǐng)調(diào)用SKReceiptRefreshRequest以確保您正在使用的收據(jù)是最新的,例如在后臺(tái)更新訂閱時(shí)。 此刷新需要網(wǎng)絡(luò)連接。


通過(guò)App Store驗(yàn)證收據(jù)

在安全服務(wù)器上通過(guò)App Store驗(yàn)證交易。

總覽

App Store收據(jù)是使用Apple證書(shū)簽名的二進(jìn)制加密文件。 為了讀取加密文件的內(nèi)容,您需要將其通過(guò)verifyReceipt端點(diǎn)。 端點(diǎn)的響應(yīng)包括可讀的JSON正文。 與App Store的通信按照RFC 4627的定義構(gòu)造為JSON字典。二進(jìn)制數(shù)據(jù)按RFC 4648的定義進(jìn)行Base64編碼。通過(guò)安全服務(wù)器驗(yàn)證與App Store的收據(jù)。 有關(guān)與App Store建立安全網(wǎng)絡(luò)連接的信息,請(qǐng)參閱Preventing Insecure Network Connections.

警告
不要從您的應(yīng)用程序調(diào)用App Store服務(wù)器的verifyReceipt端點(diǎn)。 您無(wú)法直接在用戶的設(shè)備與App Store之間建立信任的連接,因?yàn)槟鸁o(wú)法控制該連接的任何一端,這會(huì)使它容易受到中間人攻擊。

獲取收據(jù)數(shù)據(jù)

要從設(shè)備上的應(yīng)用中檢索收據(jù)數(shù)據(jù),請(qǐng)使用NSBundleappStoreReceiptURL方法找到該應(yīng)用的收據(jù),然后在Base64中對(duì)數(shù)據(jù)進(jìn)行編碼。 將此Base64編碼的數(shù)據(jù)發(fā)送到您的服務(wù)器。

/* Load the receipt from the app bundle. */
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];

if (!receipt) { 
    NSLog(@"no receipt");
    /* No local receipt -- handle the error. */ 
} else {
    /* Get the receipt in encoded format */
    NSString *encodedReceipt = [receipt base64EncodedStringWithOptions:0];
}

/* ... Send the receipt data to your server ... */

將收據(jù)數(shù)據(jù)發(fā)送到App Store

在您的服務(wù)器上,創(chuàng)建一個(gè)帶有收據(jù)數(shù)據(jù),密碼(如果收據(jù)包含自動(dòng)續(xù)訂)的JSON對(duì)象,并在requestBody中詳細(xì)說(shuō)明exclude-old-transactions鍵。

提交此JSON對(duì)象作為HTTP POST請(qǐng)求的有效負(fù)載。 在沙盒中測(cè)試您的應(yīng)用程序以及在審查應(yīng)用程序時(shí),請(qǐng)使用測(cè)試環(huán)境URL https://sandbox.itunes.apple.com/verifyReceipt。 當(dāng)您的應(yīng)用程序在App Store中上線時(shí),請(qǐng)使用生產(chǎn)URL https://buy.itunes.apple.com/verifyReceipt。 有關(guān)這些端點(diǎn)的更多信息,請(qǐng)參見(jiàn)verifyReceipt。

重要信息
首先使用生產(chǎn)網(wǎng)址驗(yàn)證收據(jù); 然后使用沙盒網(wǎng)址進(jìn)行驗(yàn)證,如果您收到21007狀態(tài)代碼。 這種方法可確保您在測(cè)試應(yīng)用程序,通過(guò)App Review進(jìn)行審查或在App Store中運(yùn)行應(yīng)用程序時(shí),不必在URL之間切換。

解析響應(yīng)

App Store的響應(yīng)有效負(fù)載是一個(gè)JSON對(duì)象,其中包含responseBody中詳細(xì)介紹的鍵和值。

in_app數(shù)組包含用戶先前購(gòu)買的不可消耗,不可更新的訂閱以及可自動(dòng)更新的訂閱項(xiàng)。 檢查響應(yīng)中這些應(yīng)用內(nèi)購(gòu)買類型的值,以根據(jù)需要驗(yàn)證交易。

對(duì)于自動(dòng)續(xù)訂的訂閱項(xiàng)目,解析響應(yīng)以獲取有關(guān)當(dāng)前有效訂閱期的信息。 當(dāng)您驗(yàn)證訂閱的收據(jù)時(shí),latest_receipt包含最新的編碼收據(jù),與請(qǐng)求中收據(jù)數(shù)據(jù)的值相同,并且latest_receipt_info包含訂閱的所有交易,包括首次購(gòu)買和后續(xù)續(xù)訂,但不包含 包括所有還原。

您可以使用這些值來(lái)檢查自動(dòng)更新訂閱是否已過(guò)期。 將這些值與expiration_intent訂閱字段一起使用以獲取到期原因。


解鎖購(gòu)買內(nèi)容

驗(yàn)證購(gòu)買后,將內(nèi)容交付給用戶。

總覽

購(gòu)買完成后,您有責(zé)任確保以編程方式將內(nèi)容提供給用戶。

識(shí)別購(gòu)買的內(nèi)容

對(duì)于啟用了應(yīng)用程序功能(例如高級(jí)訂閱)的應(yīng)用程序內(nèi)購(gòu)買產(chǎn)品,請(qǐng)?jiān)O(shè)置布爾值以啟用代碼路徑并根據(jù)需要更新用戶界面。 查看應(yīng)用程序中發(fā)生的交易的永久記錄,以確定要解鎖的功能。 每當(dāng)用戶完成購(gòu)買并在應(yīng)用啟動(dòng)時(shí),您的應(yīng)用都必須更新此布爾值。 有關(guān)進(jìn)行永久記錄的信息,請(qǐng)參閱Persisting a Purchase

例如,使用應(yīng)用程序收據(jù),您的代碼可能如下所示:

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];

// Custom method to work with receipts
BOOL rocketCarEnabled = [self receipt:receiptData
        includesProductID:@"com.example.rocketCar"];

或者,使用user defaults system

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];

定義布爾變量后,使用購(gòu)買信息在應(yīng)用程序中啟用適當(dāng)?shù)拇a路徑:

if (rocketCarEnabled) {
    // Use the rocket car.
} else {
    // Use the regular car.
}

傳送相關(guān)內(nèi)容

您的應(yīng)用必須將與購(gòu)買的產(chǎn)品相關(guān)的任何內(nèi)容交付給用戶。例如,在音樂(lè)應(yīng)用程序中購(gòu)買樂(lè)器需要傳送讓用戶演奏這些樂(lè)器所需的聲音文件。您可以將該內(nèi)容嵌入到應(yīng)用程序的捆綁軟件中,或根據(jù)需要下載。每種方法都有其優(yōu)點(diǎn)和缺點(diǎn)。

在您的應(yīng)用程序中嵌入較小的文件(最大幾兆字節(jié)),尤其是在您希望大多數(shù)用戶購(gòu)買該產(chǎn)品的情況下。您可以在用戶購(gòu)買應(yīng)用捆綁包后立即使其可用。但是,要添加或更新應(yīng)用程序捆綁包中的內(nèi)容,必須提交應(yīng)用程序的更新版本。

僅在需要時(shí)下載較大的文件。將內(nèi)容與應(yīng)用捆綁包分開(kāi)可以使應(yīng)用的初始下載量小。例如,游戲可以在其應(yīng)用包中包含第一級(jí),并允許用戶在購(gòu)買時(shí)下載其他級(jí)。假設(shè)您的應(yīng)用從服務(wù)器中獲取了產(chǎn)品標(biāo)識(shí)符列表,并且該信息未在應(yīng)用捆綁中進(jìn)行硬編碼,則無(wú)需重新提交應(yīng)用即可添加或更新應(yīng)用下載的內(nèi)容。

注意
您無(wú)法修補(bǔ)應(yīng)用二進(jìn)制文件或下載可執(zhí)行代碼。 您的應(yīng)用在提交時(shí)必須包含支持其所有功能所需的所有可執(zhí)行代碼。 如果新產(chǎn)品需要更改代碼,請(qǐng)?zhí)峤粦?yīng)用程序的更新版本。

加載本地內(nèi)容

從應(yīng)用程序包加載其他資源時(shí),請(qǐng)使用NSBundle類加載本地內(nèi)容。

NSURL *url = [[NSBundle mainBundle] URLForResource:@"rocketCar"
                                     withExtension:@"plist"];
[self loadVehicleAtURL:url];

從Apple的服務(wù)器下載托管內(nèi)容

大多數(shù)應(yīng)用程序應(yīng)使用Apple托管的內(nèi)容來(lái)下載文件。您使用Xcode中的“應(yīng)用內(nèi)購(gòu)買內(nèi)容”目標(biāo)創(chuàng)建了Apple托管的內(nèi)容包,并將其提交到App Store ConnectApple的服務(wù)器使用支持其他大規(guī)模操作(例如App Store)的相同基礎(chǔ)結(jié)構(gòu)來(lái)存儲(chǔ)您的應(yīng)用程序內(nèi)容。即使您的應(yīng)用未運(yùn)行,Apple托管的內(nèi)容也會(huì)在后臺(tái)自動(dòng)下載。

如果您需要支持舊版本的iOS或在多個(gè)平臺(tái)上共享服務(wù)器基礎(chǔ)結(jié)構(gòu),則可以選擇使用自己的服務(wù)器基礎(chǔ)結(jié)構(gòu)來(lái)托管自己的內(nèi)容。

當(dāng)用戶購(gòu)買具有關(guān)聯(lián)的Apple托管內(nèi)容的產(chǎn)品時(shí),傳遞給您的事務(wù)隊(duì)列觀察器的事務(wù)還包括一個(gè) SKDownload實(shí)例,可讓您下載關(guān)聯(lián)的內(nèi)容。

要下載內(nèi)容,請(qǐng)通過(guò)調(diào)用SKPaymentQueuestartDownloads:方法,將交易對(duì)象的downloads屬性中的下載對(duì)象添加到交易隊(duì)列中。如果downloads屬性的值為nil,則說(shuō)明該交易沒(méi)有Apple托管的內(nèi)容。與下載應(yīng)用程序不同,下載內(nèi)容不會(huì)自動(dòng)要求Wi-Fi連接以獲取大于特定大小的內(nèi)容。避免在沒(méi)有用戶明確行動(dòng)的情況下使用蜂窩網(wǎng)絡(luò)下載大文件。

注意
另外,您可以使用按需資源(ODR)獲得更大的靈活性來(lái)下載應(yīng)用程序中的數(shù)據(jù)。 ODRApple托管的服務(wù),一旦您使用應(yīng)用收據(jù)確認(rèn)用戶的購(gòu)買后,便可以使用該服務(wù)存儲(chǔ)應(yīng)用內(nèi)購(gòu)買數(shù)據(jù),以供用戶下載內(nèi)容。 與SKDownload相比,此替代方法的優(yōu)勢(shì)在于ODR不需要您調(diào)用即可恢復(fù)事務(wù)并驗(yàn)證用戶以下載Apple服務(wù)器上托管的內(nèi)容。

在交易隊(duì)列觀察器上實(shí)現(xiàn) paymentQueue:updatedDownloads:方法,以響應(yīng)下載狀態(tài)的更改,例如通過(guò)更新UI中的進(jìn)度。如果下載失敗,請(qǐng)使用其error屬性中的信息將錯(cuò)誤呈現(xiàn)給用戶。

確保您的應(yīng)用正常處理錯(cuò)誤。例如,如果設(shè)備在下載期間磁盤(pán)空間不足,請(qǐng)給用戶選擇放棄部分下載或在空間可用時(shí)稍后繼續(xù)下載的選項(xiàng)。

在下載內(nèi)容時(shí),使用 progresstimeRemaining屬性的值更新用戶界面。您可以從用戶界面中使用SKPaymentQueue的 pauseDownloads:, resumeDownloads:,和 cancelDownloads:方法來(lái)讓用戶控制正在進(jìn)行的下載。使用downloadState屬性確定下載是否已完成。不要使用下載對(duì)象的progresstimeRemaining屬性來(lái)檢查其狀態(tài);這些屬性用于更新您的UI。

重點(diǎn)
完成交易之前,請(qǐng)下載所有Apple托管的內(nèi)容。 事務(wù)完成后,其下載對(duì)象將無(wú)法使用。

在iOS中,您的應(yīng)用可以管理下載的文件。 StoreKit框架會(huì)在未設(shè)置備份標(biāo)記的情況下將這些文件保存在Caches目錄中。 下載完成后,您的應(yīng)用程序負(fù)責(zé)將這些文件移動(dòng)到適當(dāng)?shù)奈恢谩?對(duì)于如果設(shè)備磁盤(pán)空間不足(然后由您的應(yīng)用再次下載)可以刪除的內(nèi)容,請(qǐng)將文件保留在Caches目錄中。 否則,將文件移動(dòng)到Documents文件夾并設(shè)置標(biāo)志以將其從用戶備份中排除。

NSError *error;
BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES]
                              forKey:NSURLIsExcludedFromBackupKey
                               error:&error];
if (!success) { /* Handle error... */ }

macOS中,系統(tǒng)管理下載的文件; 您的應(yīng)用無(wú)法直接移動(dòng)或刪除它們。 要在下載后找到內(nèi)容,請(qǐng)使用下載對(duì)象的contentUR屬性。 要在隨后的啟動(dòng)中找到文件,請(qǐng)使用SKDownloadcontentURLForProductID:類方法。 要?jiǎng)h除文件,請(qǐng)使用deleteContentForProductID:類方法。 有關(guān)閱讀應(yīng)用收據(jù)的信息,請(qǐng)參閱 Validating Receipts with the App Store。

從自己的服務(wù)器上下載內(nèi)容

與您的應(yīng)用程序和服務(wù)器之間的所有其他交互一樣,從您自己的服務(wù)器下載內(nèi)容的過(guò)程的細(xì)節(jié)和機(jī)制也是您的責(zé)任。 通信至少包括以下步驟:

  1. 您的應(yīng)用會(huì)將收據(jù)發(fā)送到您的服務(wù)器并請(qǐng)求內(nèi)容。
  2. 您的服務(wù)器將驗(yàn)證收據(jù)以確認(rèn)已購(gòu)買了內(nèi)容,如Validating Receipts with the App Store中所述。
  3. 假設(shè)收據(jù)有效,您的服務(wù)器將使用內(nèi)容響應(yīng)您的應(yīng)用。

確保您的應(yīng)用正常處理錯(cuò)誤。 例如,如果設(shè)備在下載期間磁盤(pán)空間不足,請(qǐng)給用戶選擇放棄部分下載或在空間可用時(shí)稍后繼續(xù)下載的選項(xiàng)。

考慮如何托管內(nèi)容以及應(yīng)用程序與服務(wù)器通信的安全性。 有關(guān)更多信息,請(qǐng)參見(jiàn)Security Overview


保存購(gòu)買記錄

保持購(gòu)買記錄,以繼續(xù)根據(jù)需要提供該產(chǎn)品。

總覽

在提供產(chǎn)品之后,您的應(yīng)用應(yīng)記錄購(gòu)買的永久記錄。您的應(yīng)用使用該永久記錄來(lái)繼續(xù)使產(chǎn)品在發(fā)布時(shí)可用并恢復(fù)購(gòu)買。您的應(yīng)用的持久性策略取決于您銷售的產(chǎn)品類型:

  • 對(duì)于非消耗性產(chǎn)品和自動(dòng)續(xù)訂的訂閱,請(qǐng)使用應(yīng)用收據(jù)作為您的永久記錄。如果應(yīng)用收據(jù)不可用,請(qǐng)使用用戶默認(rèn)系統(tǒng)或iCloud保留永久記錄。
  • 對(duì)于非續(xù)訂訂閱,請(qǐng)使用iCloud或您自己的服務(wù)器來(lái)保留永久記錄。
  • 對(duì)于消耗品,您的應(yīng)用會(huì)更新其內(nèi)部狀態(tài)以反映購(gòu)買情況。確保更新后的狀態(tài)是支持狀態(tài)保留的對(duì)象的一部分(在iOS中),或者在應(yīng)用啟動(dòng)時(shí)手動(dòng)保留狀態(tài)(在iOSmacOS中)。

使用用戶默認(rèn)系統(tǒng)或iCloud時(shí),您的應(yīng)用程序可以存儲(chǔ)一個(gè)值(例如數(shù)字或布爾值)或交易收據(jù)的副本。在macOS中,用戶可以使用defaults命令編輯用戶默認(rèn)系統(tǒng)。存儲(chǔ)收據(jù)需要更多的應(yīng)用程序邏輯,但可以防止永久記錄被篡改。

注意
通過(guò)iCloud持久保存時(shí),您的應(yīng)用程序的持久記錄會(huì)在設(shè)備之間同步,但是您的應(yīng)用程序負(fù)責(zé)在其他設(shè)備上下載任何關(guān)聯(lián)的內(nèi)容。

使用應(yīng)用收據(jù)保存購(gòu)買

應(yīng)用收據(jù)包含由Apple加密簽名的用戶購(gòu)買記錄。 有關(guān)更多信息,請(qǐng)參見(jiàn)Choosing a Receipt Validation Technique。

付款后,有關(guān)消費(fèi)品的信息就會(huì)添加到收據(jù)中,并保留在收據(jù)中,直到您完成交易為止。 完成交易后,下次更新收據(jù)時(shí)(例如用戶下次購(gòu)物),將刪除此信息。

購(gòu)買產(chǎn)品時(shí),有關(guān)所有其他購(gòu)買信息的信息會(huì)添加到收據(jù)中,并無(wú)限期保留在收據(jù)中。

在UserDefault或者iCloud中保存一個(gè)值

要將信息存儲(chǔ)在UserDefault值或iCloud中,請(qǐng)?jiān)O(shè)置密鑰的值。

#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif

[storage setBool:YES forKey:@"enable_rocket_car"];
[storage setObject:@15 forKey:@"highest_unlocked_level"];

使用自己的服務(wù)器保存購(gòu)買記錄

將收據(jù)的副本以及憑據(jù)或標(biāo)識(shí)符發(fā)送到您的服務(wù)器,以便您可以跟蹤哪些收據(jù)屬于特定用戶。 例如,讓用戶使用用戶名和密碼向您的服務(wù)器標(biāo)識(shí)自己。 不要使用UIDeviceidentifierForVendor屬性。 不同的設(shè)備對(duì)此屬性具有不同的值,因此您不能使用它來(lái)識(shí)別和恢復(fù)同一用戶在不同設(shè)備上進(jìn)行的購(gòu)買。


完成交易

完成transaction以完成購(gòu)買過(guò)程。

總覽

完成交易將告訴StoreKit購(gòu)買所需的一切均已完成。 未完成的交易會(huì)一直保留在隊(duì)列中,直到完成為止。因此,您應(yīng)在每次啟動(dòng)應(yīng)用程序時(shí)添加事務(wù)隊(duì)列觀察器,以使應(yīng)用程序能夠完成交易。 您的應(yīng)用程序必須完成每筆交易,無(wú)論成功還是失敗。

完成交易之前,請(qǐng)執(zhí)行以下所有操作:

  1. 保存購(gòu)買記錄。
  2. 下載相關(guān)內(nèi)容。
  3. 更新應(yīng)用的用戶界面,以便用戶可以訪問(wèn)產(chǎn)品。

要完成交易,請(qǐng)?jiān)谥Ц蛾?duì)列上調(diào)用finishTransaction:方法。

SKPaymentTransaction *transaction = <# The current transaction #>;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

完成交易后,請(qǐng)勿對(duì)其執(zhí)行任何操作或進(jìn)行任何交付產(chǎn)品的工作。 如果還有任何工作要做,則說(shuō)明您的應(yīng)用尚未準(zhǔn)備好完成交易。

重要信息
在事務(wù)實(shí)際完成之前,請(qǐng)勿嘗試調(diào)用finishTransaction:方法,并嘗試使用應(yīng)用程序中的其他機(jī)制來(lái)跟蹤未完成的事務(wù)。 StoreKit并非設(shè)計(jì)用于這種方式,這樣做會(huì)阻止您的應(yīng)用下載Apple托管的內(nèi)容,并可能導(dǎo)致其他問(wèn)題。


處理退款通知

響應(yīng)在客戶退款期間為消耗性,非消耗性和非更新性訂閱產(chǎn)品創(chuàng)建的通知。

總覽

當(dāng)客戶收到應(yīng)用內(nèi)購(gòu)買的退款時(shí),App Store Server會(huì)發(fā)送近乎實(shí)時(shí)的通知。 如果您跨多個(gè)平臺(tái)提供內(nèi)容(例如游戲中的寶石或硬幣),并且在服務(wù)器上更新玩家?guī)粲囝~,則接收退款通知非常重要。 通過(guò)解釋和處理退款信息來(lái)響應(yīng)退款通知,并在應(yīng)用程序中告知客戶您因退款而采取的任何措施。

接收一次性購(gòu)買的客戶退款通知

客戶以多種方式要求退款,例如:

  • 聯(lián)系A(chǔ)pple客戶支持并要求退款
  • 登錄并使用Apple的自助服務(wù)工具reportaproblem.apple.com要求退款
  • 要求他們的付款方式發(fā)行人退款

當(dāng)App Store處理退款時(shí),App Store服務(wù)器會(huì)通過(guò)您配置的URL向您的服務(wù)器發(fā)送REFUND通知。 您的服務(wù)器必須使用200響應(yīng)碼來(lái)響應(yīng)該帖子。 要啟用通知,請(qǐng)參閱Enabling App Store Server Notifications。

REFUND通知僅適用于消耗性,非消耗性和非續(xù)訂性訂閱。 要檢測(cè)自動(dòng)續(xù)訂訂閱的退款,請(qǐng)參閱檢測(cè)退款。

解釋和處理退款通知

您的服務(wù)器負(fù)責(zé)解析和解釋來(lái)自App Store Server的所有通知。 對(duì)于“退款”通知,請(qǐng)從響應(yīng)中標(biāo)識(shí)特定交易,產(chǎn)品ID和相關(guān)日期:

  • 通過(guò)檢查purchase_date以選擇最新交易,從而在unified_receipt.latest_receipt_info中找到product_id的最新交易。
  • App Store發(fā)出退款的日期在此交易的cancel_date_ms字段中。

有關(guān)響應(yīng)的更多信息,請(qǐng)參閱App Store Server Notifications。

當(dāng)您收到退款通知時(shí),您有責(zé)任為每筆退款交易存儲(chǔ),監(jiān)控并采取適當(dāng)?shù)拇胧?例如,您可以構(gòu)建自己的游戲內(nèi)貨幣平衡邏輯,該邏輯通過(guò)將通知鏈接到玩家?guī)艋驎?huì)話來(lái)處理退款交易。

重要信息
當(dāng)您使用包含退款交易的收據(jù)來(lái)調(diào)用verifyReceipt端點(diǎn)時(shí),JSON響應(yīng)中不存在退款交易,自動(dòng)續(xù)訂訂閱除外。

通過(guò)在應(yīng)用程序中顯示上下文消息通知客戶有關(guān)退款所采取的任何措施。

識(shí)別退款濫用

通過(guò)將“退款”通知映射到服務(wù)器上的玩家?guī)?,減少退款濫用并識(shí)別重復(fù)的退款購(gòu)買。 監(jiān)視和分析您的數(shù)據(jù)以識(shí)別可疑的退款活動(dòng)。

如果您跨多個(gè)平臺(tái)提供內(nèi)容,請(qǐng)?jiān)诜?wù)器上更新用戶帳戶的余額。 使用App Store Server通知來(lái)獲取影響客戶的交易的近實(shí)時(shí)狀態(tài)更新。


恢復(fù)購(gòu)買的產(chǎn)品

為用戶提供可以在您的應(yīng)用中恢復(fù)購(gòu)買的功能,以維持對(duì)購(gòu)買內(nèi)容的訪問(wèn)權(quán)限。

總覽

用戶有時(shí)需要還原購(gòu)買的內(nèi)容,例如當(dāng)他們升級(jí)到新手機(jī)時(shí)。 在您的應(yīng)用程序中包含一些機(jī)制(例如“還原購(gòu)買”按鈕),以使他們還原購(gòu)買的內(nèi)容。

重要信息
不要自動(dòng)恢復(fù)購(gòu)買,特別是在啟動(dòng)應(yīng)用程序時(shí)。 恢復(fù)購(gòu)買會(huì)提示用戶輸入App Store憑據(jù),這會(huì)中斷您的應(yīng)用流程。

在大多數(shù)情況下,您只需要刷新應(yīng)用收據(jù)并交付收據(jù)上列出的產(chǎn)品即可。刷新后的收據(jù)包含用戶從該應(yīng)用商店帳戶登錄的任何設(shè)備上在該應(yīng)用中購(gòu)買商品的記錄。但是,在給定情況下,應(yīng)用可能需要其他方法:

  • 您使用Apple托管的內(nèi)容-恢復(fù)已完成的交易,為您的應(yīng)用程序提供下載內(nèi)容所使用的交易對(duì)象。
  • 您需要在沒(méi)有收據(jù)的設(shè)備上支持您的應(yīng)用程序-還原已完成的交易。
  • 您的應(yīng)用使用非續(xù)訂訂閱-您的應(yīng)用負(fù)責(zé)恢復(fù)過(guò)程。
    刷新收據(jù)不會(huì)創(chuàng)建新的交易;它會(huì)從App Store請(qǐng)求收據(jù)的最新副本。僅刷新一次收據(jù);連續(xù)刷新多次具有相同的結(jié)果。

恢復(fù)已完成的事務(wù)會(huì)為以前完成的每個(gè)事務(wù)創(chuàng)建一個(gè)新事務(wù),實(shí)質(zhì)上是為您的事務(wù)隊(duì)列觀察者重放歷史記錄。您的應(yīng)用程序保持其自身狀態(tài),以跟蹤其為何還原已完成的交易以及如何處理它們。多次還原會(huì)為每個(gè)完成的事務(wù)創(chuàng)建多個(gè)還原的事務(wù)。

注意
如果用戶嘗試購(gòu)買已經(jīng)購(gòu)買的產(chǎn)品,則App Store會(huì)創(chuàng)建常規(guī)交易而不是還原交易,但是不會(huì)再次向用戶收費(fèi)。 與原始交易一樣,解鎖這些交易的內(nèi)容。

為用戶提供對(duì)再次下載的內(nèi)容的適當(dāng)級(jí)別的控制。 例如,不要同時(shí)自動(dòng)下載三年的日?qǐng)?bào)或數(shù)百兆的游戲級(jí)別。

刷新應(yīng)用收據(jù)

創(chuàng)建收據(jù)刷新請(qǐng)求,設(shè)置委托,然后啟動(dòng)請(qǐng)求。 該請(qǐng)求支持可選屬性,用于在測(cè)試期間獲取各種狀態(tài)的收據(jù),例如過(guò)期的收據(jù)。 有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)SKReceiptRefreshRequestinitWithReceiptProperties:方法。

request = [[SKReceiptRefreshRequest alloc] init];
request.delegate = self;
[request start];

刷新應(yīng)用收據(jù)后,請(qǐng)對(duì)其進(jìn)行檢查并交付添加到收據(jù)中的所有產(chǎn)品。

恢復(fù)已完成的交易

您的應(yīng)用程序通過(guò)調(diào)用SKPaymentQueuerestoreCompletedTransactions方法開(kāi)始恢復(fù)已完成的交易。 此調(diào)用將請(qǐng)求發(fā)送到App Store,以恢復(fù)您所有應(yīng)用程序已完成的交易。 如果您的應(yīng)用為其付款請(qǐng)求的applicationUsername屬性設(shè)置了一個(gè)值,則在還原交易時(shí),可以使用restoreCompletedTransactionsWithApplicationUsername:方法提供相同的信息。

App Store會(huì)生成一個(gè)新交易,以還原每個(gè)先前完成的交易。 恢復(fù)的事務(wù)引用原始事務(wù):SKPaymentTransaction的實(shí)例具有originalTransaction屬性,而收據(jù)中的條目具有original_transaction_id字段值。

對(duì)于恢復(fù)的購(gòu)買,日期字段的含義略有不同。 有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)responseBody.Receipt.In_app中的purchase_dateoriginal_purchase_date字段。

StoreKit為每個(gè)已還原的事務(wù)調(diào)用狀態(tài)為SKPaymentTransactionStateRestored的事務(wù)隊(duì)列觀察器,如處理事務(wù)中所述。 此時(shí)要執(zhí)行的操作取決于應(yīng)用程序的設(shè)計(jì)。

如果您的應(yīng)用使用應(yīng)用收據(jù)并且沒(méi)有Apple托管的內(nèi)容,則不需要此代碼,因?yàn)槟膽?yīng)用不會(huì)還原已完成的交易。 立即完成所有還原的事務(wù)。

如果您的應(yīng)用使用應(yīng)用收據(jù)并且具有Apple托管的內(nèi)容,請(qǐng)讓用戶選擇要還原的產(chǎn)品,然后再開(kāi)始還原過(guò)程。 在還原過(guò)程中,請(qǐng)先下載用戶選擇的內(nèi)容,然后再完成這些事務(wù),并立即完成任何其他事務(wù)。

NSMutableArray *productIDsToRestore = <# From the user #>;
SKPaymentTransaction *transaction = <# Current transaction #>;

if ([productIDsToRestore containsObject:transaction.transactionIdentifier]) {
// Re-download the Apple-hosted content
}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

如果您的應(yīng)用不使用應(yīng)用收據(jù),則會(huì)在還原所有已完成的交易時(shí)對(duì)其進(jìn)行檢查。 它使用與原始購(gòu)買邏輯相似的代碼路徑來(lái)使產(chǎn)品可用,然后完成交易。 具有多個(gè)產(chǎn)品(尤其是具有相關(guān)內(nèi)容的產(chǎn)品)的應(yīng)用程序使用戶可以選擇要還原的產(chǎn)品,而不是還原所有內(nèi)容。 這些應(yīng)用程序通過(guò)立即完成而不還原它們來(lái)跟蹤要還原的已完成事務(wù)以及要忽略的事務(wù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容